使用Lazy优雅的解决构造函数中的异步需求
使用Lazy<>优雅的解决构造函数中的异步需求
前世今生
从netframework 4.0开始,C#开始支持延迟初始化,通过Lazy关键字,我们可以声明某个对象为仅仅当第一次使用的时候,再初始化,如果一直没有调用,那就不初始化,省去了一部分不必要的开销,提升了效率
线程安全性
默认情况下,该类的所有公共和受保护成员 Lazy 都是线程安全的,并且可以从多个线程并发使用。 可以使用类型构造函数的参数(可选)和每个实例删除这些线程安全保证
说到天生具备的线程安全,就来一个以Lazy依托写的懒汉式泛型单例,如下
public class Singleton<T> where T : class, new() { public static T Instance = new Lazy<T>(() => new T()).Value; }
起因
说了这么多的废话,现在回归正题,在使用QuartZ.Net的时候需要编写的一个管理各个Job的JobManager, 如下
public interface IQuartzJobService { /// <summary> /// 添加Job /// </summary> void AddJob(IJobDetail jobDetail, ITrigger trigger); /// <summary> /// 暂停所有任务 /// </summary> Task PauseAll(); /// <summary> /// 恢复所有任务 /// </summary> Task ResumeAll(); /// <summary> /// 停止指定的任务 /// </summary> Task PauseJob(string key); /// <summary> /// 恢复指定的任务 /// </summary> Task ResumeJob(string key); /// <summary> /// 暂停指定的任务组 /// </summary> Task PauseJobs(string group); /// <summary> /// 恢复指定的任务组 /// </summary> Task ResumeJobs(string group); /// <summary> /// 停止调度器 /// </summary> Task Shutdown(); }
那么问题来了, 现实这些接口都需要IScheduler的支持,从IOC中拿到的ISchedulerFactory的GetScheduler方法是一个异步方法,为了图个方便又下面几种
private readonly ILogger<QuartzJobService> _logger; private readonly IScheduler _scheduler; public QuartzJobService(ILogger<QuartzJobService> logger, ISchedulerFactory factory) { _logger = logger; // 1 //_scheduler = factory.GetScheduler().Result; // 2 //Task.Run(async () => _scheduler = await factory.GetScheduler().ConfigureAwait(false)); }
也行吧, 反正使用是没有什么问题的,如果碰到这个方法耗时非常久或者需要拿到这个耗时方法的返回结果来进行其他部分成员字段创建,多少就又那么一些难办或是不优雅
使用今天讲解的主角Lazy来办这个事,如下
private readonly ILogger<QuartzJobService> _logger; private readonly Lazy<Task<IScheduler>> _scheduler; public QuartzJobService(ILogger<QuartzJobService> logger, ISchedulerFactory factory) { _logger = logger; _scheduler = new Lazy<Task<IScheduler>>(factory.GetScheduler()); //_scheduler = new Lazy<Task<IScheduler>>(async () => //{ // var scheduler = await factory.GetScheduler(); // return scheduler; //}); }
我调试的过程中是使用的下面带lambda的,为了方便验证它会调用多少次, 我通过以下代码运行
builder.Services.SetupQuartz(options => { var jobKey = JobKey.Create(nameof(SimpleJob)); options.AddJob<SimpleJob>(jobKey); options.AddTrigger(configure => { configure.ForJob(jobKey) .WithCronSchedule("0/3 * * * * ?") .StartNow(); }); jobKey = JobKey.Create(nameof(SimpleJob1)); options.AddJob<SimpleJob1>(jobKey); options.AddTrigger(configure => { configure.ForJob(jobKey) .WithCronSchedule("0/5 * * * * ?") .StartNow(); }); }); var app = builder.Build(); var quartz = app.Services.GetRequiredService<IQuartzJobService>(); quartz.PauseJob(nameof(SimpleJob)); quartz.ResumeJob(nameof(SimpleJob)); quartz.PauseJob(nameof(SimpleJob1)); quartz.ResumeJob(nameof(SimpleJob1)); //...... public async Task PauseJob(string key) { var scheduler = await _scheduler.Value; await scheduler.PauseJob(JobKey.Create(key)); } public async Task ResumeJob(string key) { var scheduler = await _scheduler.Value; await scheduler.ResumeJob(JobKey.Create(key)); }
验证结果如所料那样, 第一次调用Pause时调用Lazy<>里面的lambda,然后剩下三次都不会再去调用Lazy<>里面的lambda了, 这个在一些特定的场合时非常省心的, 特别时不想再写一个其他的InitializeAsync类似的方法来给Constructor解围时,而且DI中用了类似InitializeAsync之类的辅助类异步初始化感觉是怪怪的,所以这个Lazy是很实用的, 突出一个按需使用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
2019-06-28 C#扩展枚举的别名