Use Lazy to elegantly solve the asynchronous requirements in the constructor

Use Lazy<>to elegantly solve the asynchronous requirements in the constructor

Past life and present life

Starting from netframework 4.0, C # has supported delayed initialization. Through the Lazy keyword, we can declare that an object is reinitialized only when it is used for the first time. If it has not been called, it will not be initialized, which saves some unnecessary overhead and improves efficiency

Thread safety

By default, all public and protected members of this class Lazy They are thread safe and can be used concurrently from multiple threads. You can use the parameters of the type constructor (optional) and each instance to remove these thread safety guarantees

Speaking of the inherent thread safety, let's take Lazy's lazy generic example, as follows

 public class Singleton<T> where T : class, new() { public static T Instance = new Lazy<T>(() => new T()). Value; }

cause

Having said so much nonsense, let's get back to business and use QuartZ The JobManager that needs to be written to manage each job is as follows

 public interface IQuartzJobService { /// <summary> ///Add Job /// </summary> void AddJob(IJobDetail jobDetail, ITrigger trigger); /// <summary> ///Pause all tasks /// </summary> Task PauseAll(); /// <summary> ///Resume all tasks /// </summary> Task ResumeAll(); /// <summary> ///Stop the specified task /// </summary> Task PauseJob(string key); /// <summary> ///Resume the specified task /// </summary> Task ResumeJob(string key); /// <summary> ///Suspend the specified task group /// </summary> Task PauseJobs(string group); /// <summary> ///Restore the specified task group /// </summary> Task ResumeJobs(string group); /// <summary> ///Stop scheduler /// </summary> Task Shutdown(); }

The problem is that these interfaces need the support of IScheduler. The GetScheduler method of ISchedulerFactory obtained from IOC is an asynchronous method, and the following methods are added for the convenience of illustration

 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)); }

OK, anyway, there is no problem in using it. If this method takes a long time or you need to get the return results of this time-consuming method to create other member fields, it is somewhat difficult or inelegant

Use today's main character Lazy to do this, as follows

 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; //}); }

I use the following lambda during debugging. To verify how many times it will be called, I run it through the following code

 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)); }

The verification result is as expected. The first time you call Pause, you call the lambda in Lazy<>, and then you will not call the lambda in Lazy<>for the remaining three times. This is very convenient in some specific situations, especially when you don't want to write another InitializeAsync like method to solve the constructor's problem, In addition, DI uses auxiliary classes such as InitializeAsync for asynchronous initialization, which feels strange, so this Lazy is very practical, highlighting an on-demand

posted @ 2022-06-28 16:42   illegal keyword   Reading( two hundred and sixty-nine Comments( zero edit   Collection   report