如何在 ASP.NET Core 中使用 Quartz.NET 執(zhí)行任務(wù)調(diào)度
當(dāng)我們在web開發(fā)中,常常會遇到這么一個需求,在后臺執(zhí)行某一項具體的任務(wù),具體的說就是這些任務(wù)必須在后臺定時執(zhí)行。
Quartz.NET 是一個開源的 JAVA 移植版,它有著悠久的歷史并且提供了強大的 Cron 表達(dá)式,這篇我們就來討論如何在 ASP.NET Core 中使用 Quartz.NET 去執(zhí)行一些后臺任務(wù)。
安裝 Quartz.NET
要想使用 Quartz.NET,你可以使用 Visual Studio 2019 中的 NuGet package manager 可視化界面進(jìn)行安裝,或者通過 NuGet package manager console 命令行輸入如下命令:
Install-Package?Quartz
Quartz.NET 中的Job,triggers 和 Schedulers
Quartz.NET 里有三個非常重要的概念:任務(wù),觸發(fā)器 和 調(diào)度器,對應(yīng)著 Job,Trigger 和 Schedulers,Job 表示一個你需要被執(zhí)行的任務(wù),任務(wù)中可以寫上你的業(yè)務(wù)邏輯代碼,Job 就是一個實現(xiàn)了 IJob 接口的子類,如下代碼所示:
????class?Job?:?IJob
????{
????????public?Task?Execute(IJobExecutionContext?context)
????????{
????????????throw?new?NotImplementedException();
????????}
????}
Trigger 通常用于指定一個 job 是如何被調(diào)度的?什么意思呢?比如說:這個job是按天執(zhí)行?還是按小時執(zhí)行?還是按秒執(zhí)行?值得注意的是因為支持了 Cron 表達(dá)式,還能夠?qū)崿F(xiàn)更加超級復(fù)雜的調(diào)度邏輯。
Scheduler 通常按照你預(yù)先設(shè)置的調(diào)度規(guī)則將 job 丟給它的任務(wù)隊列,并按照 trigger 規(guī)則輪詢?nèi)缓髨?zhí)行任務(wù)。
創(chuàng)建 Scheduler
在 Quartz.NET 中可以創(chuàng)建多個 Scheduler,但為了演示目的我就創(chuàng)建一個 Scheduler 啦,下面的代碼展示了如何去創(chuàng)建 Scheduler。
var?scheduler?=?StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult();
一旦使用上面的代碼創(chuàng)建好了 Scheduler,接下來就可以將其作為服務(wù)注入到 Asp.net Core 的 IOC 容器中,實現(xiàn)代碼如下:
????????public?void?ConfigureServices(IServiceCollection?services)
????????{
????????????var?scheduler?=?StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult();
????????????services.AddSingleton(scheduler);
????????????services.AddRazorPages();
????????}
開啟和停止 scheduler
為了方便實現(xiàn) 開啟 和 停止 功能,我準(zhǔn)備封裝一個 hosting service 類,做法就是從 IHostingService 接口派生出一個 CustomQuartzHostedService 類,完整代碼如下:
public?class?CustomQuartzHostedService?:?IHostedService
{
????????private?readonly?IScheduler?_scheduler;
????????public?CustomQuartzHostedService(IScheduler?scheduler)
????????{
????????????_scheduler?=?scheduler;
????????}
????????public?async?Task?StartAsync(CancellationToken?cancellationToken)
????????{
????????????await?_scheduler?.Start(cancellationToken);
????????}
????????public?async?Task?StopAsync(CancellationToken?cancellationToken)
????????{
????????????await?_scheduler?.Shutdown(cancellationToken);
????????}
?}
有了這個自定義類,接下來把這個類也注入到 servcies collection 中,實現(xiàn)代碼如下:
????????public?void?ConfigureServices(IServiceCollection?services)
????????{
????????????var?scheduler?=?StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult();
????????????services.AddSingleton(scheduler);
????????????services.AddHostedService();
????????????services.AddRazorPages();
????????}
創(chuàng)建 job
正如之前說到的,job 是一個實現(xiàn)了 IJob 接口 并且實現(xiàn)了 Execute() 的類,這個 Execute() 方法接收一個 IJobExecutionContext 參數(shù)。
下面的代碼片段展示了這個 Job 類包含了一個異步方式的 Execute() 方法,這個方法中包含的代碼就是你需要執(zhí)行的業(yè)務(wù)邏輯。
????[DisallowConcurrentExecution]
????public?class?NotificationJob?:?IJob
????{
????????private?readonly?ILogger?_logger;
????????public?NotificationJob(ILogger?logger )
????????{
????????????_logger?=?logger;
????????}
????????public?Task?Execute(IJobExecutionContext?context)
????????{
????????????_logger.LogInformation("Hello?world!");
????????????return?Task.CompletedTask;
????????}
????}
創(chuàng)建 job 工廠
如果要定義一個 Job 工廠,則必須要實現(xiàn) IJobFactory 接口中的 NewJob() 和 ReturnJob() 方法,下面的代碼片段展示了如何去 創(chuàng)建 和 返回 job 的 factory 類。
????public?class?CustomQuartzJobFactory?:?IJobFactory
????{
????????private?readonly?IServiceProvider?_serviceProvider;
????????public?CustomQuartzJobFactory(IServiceProvider?serviceProvider)
????????{
????????????_serviceProvider?=?serviceProvider;
????????}
????????public?IJob?NewJob(TriggerFiredBundle?triggerFiredBundle,
????????IScheduler?scheduler)
????????{
????????????var?jobDetail?=?triggerFiredBundle.JobDetail;
????????????return?(IJob)_serviceProvider.GetService(jobDetail.JobType);
????????}
????????public?void?ReturnJob(IJob?job)?{?}
????}
值得注意的是,這里我并沒有實現(xiàn) job 池,如果你想實現(xiàn)這個功能,你需要修改以下 NewJob() 方法 并且重寫 ReturnJob() 方法。
創(chuàng)建 JobMetadata 存儲你的 job 元數(shù)據(jù)
我準(zhǔn)備定義一個 JobMetadata 類去存儲和job 相關(guān)聯(lián)的元數(shù)據(jù),比如說:job的id,job的name 等等,下面的代碼展示了如何定義這么一個類。
????public?class?JobMetadata
????{
????????public?Guid?JobId?{?get;?set;?}
????????public?Type?JobType?{?get;?}
????????public?string?JobName?{?get;?}
????????public?string?CronExpression?{?get;?}
????????public?JobMetadata(Guid?Id,?Type?jobType,?string?jobName,
????????string?cronExpression)
????????{
????????????JobId?=?Id;
????????????JobType?=?jobType;
????????????JobName?=?jobName;
????????????CronExpression?=?cronExpression;
????????}
????}
使用 hosted service 封裝 Scheduler 的start和stop
接下來,我需要豐富一下 CustomQuartzHostedService 類,完整的代碼清單如下。
????public?class?CustomQuartzHostedService?:?IHostedService
????{
????????private?readonly?ISchedulerFactory?schedulerFactory;
????????private?readonly?IJobFactory?jobFactory;
????????private?readonly?JobMetadata?jobMetadata;
????????public?CustomQuartzHostedService(ISchedulerFactory?schedulerFactory,JobMetadata?jobMetadata,IJobFactory?jobFactory)
????????{
????????????this.schedulerFactory?=?schedulerFactory;
????????????this.jobMetadata?=?jobMetadata;
????????????this.jobFactory?=?jobFactory;
????????}
????????public?IScheduler?Scheduler?{?get;?set;?}
????????public?async?Task?StartAsync(CancellationToken?cancellationToken)
????????{
????????????Scheduler?=?await?schedulerFactory.GetScheduler();
????????????Scheduler.JobFactory?=?jobFactory;
????????????var?job?=?CreateJob(jobMetadata);
????????????var?trigger?=?CreateTrigger(jobMetadata);
????????????await?Scheduler.ScheduleJob(job,?trigger,?cancellationToken);
????????????await?Scheduler.Start(cancellationToken);
????????}
????????public?async?Task?StopAsync(CancellationToken?cancellationToken)
????????{
????????????await?Scheduler?.Shutdown(cancellationToken);
????????}
????????private?ITrigger?CreateTrigger(JobMetadata?jobMetadata)
????????{
????????????return?TriggerBuilder.Create()
?????????????????????????????????.WithIdentity(jobMetadata.JobId.ToString())
?????????????????????????????????.WithCronSchedule(jobMetadata.CronExpression)
?????????????????????????????????.WithDescription($"{jobMetadata.JobName}")
?????????????????????????????????.Build();
????????}
????????private?IJobDetail?CreateJob(JobMetadata?jobMetadata)
????????{
????????????return?JobBuilder
????????????.Create(jobMetadata.JobType)
????????????.WithIdentity(jobMetadata.JobId.ToString())
????????????.WithDescription($"{jobMetadata.JobName}")
????????????.Build();
????????}
????}
接下來再看一下 ?Startup.ConfigureServices 方法下的完整代碼。
public?void?ConfigureServices(IServiceCollection?services)
{
????services.AddRazorPages();
????services.AddSingleton();
????services.AddSingleton();
????services.AddSingleton();
????services.AddSingleton(new?JobMetadata(Guid.NewGuid(),?typeof(NotificationJob),"Notification?Job",?"0/10?*?*?*?*??"));
????services.AddHostedService();
}???
這就是所有要做的事情,接下來運行應(yīng)用程序,你會觀察到 NotificationJob 的 Execute() 方法會每 10s 執(zhí)行一次。
如何你的應(yīng)用程序中需要有任務(wù)調(diào)用的功能,現(xiàn)在開始可以不需要使用Timer了,采用強大的 Quartz.NET 即可,而且還有一個????的功能就是:你可以把 job 持久化到 SQL Server, PostgreSQL, SQLite 中,太強大了。
譯文鏈接:https://www.infoworld.com/article/3529418/how-to-schedule-jobs-using-quartznet-in-aspnet-core.html
