一、前言
近期專案中遇到一些需求,需要定時寫入資料庫,定時重新整理快取的問題,因此需要引入任務排程機制。
我的選擇是使用 Quartz.Net,使用的版本是 3.2.4
這裡強調一點:3.x的版本與2.x的版本使用方式有一定的差別,需要注意一下!!!
什麼是Quartz.NET? Quartz.NET官方文件
Quartz.NET 是一個功能齊全的開源作業排程系統,可用於從最小的應用程式到大型企業系統。
二、Quartz.Net機制圖
三、.Net Core中引入Quartz
新建.Net Core 類庫專案命名為 MCronJob
建立完成後引入Quartz.Net
在Package Manager Console輸入如下命令 安裝Quartz包
Install-Package Quartz
由Quartz.Net關係圖可知,我們需要JobFactory和實際任務類HelloJob
建立 CronJobFactory 類並實現 IJobFactory介面 ------------------任務工廠
public class CronJobFactory : IJobFactory { public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { throw new NotImplementedException(); } public void ReturnJob(IJob job) { throw new NotImplementedException(); } }
這裡打個問號?NewJob和ReturnJob兩個方法用來做什麼?---------我是伏筆①
建立HelloJob類並實現 IJob介面 ---------------------------自定義的實際任務
public class HelloJob : IJob { public async Task Execute(IJobExecutionContext context) { await Console.Out.WriteLineAsync($"{DateTime.Now:HH:mm:ss}--Hello World!"); } }
有了任務工廠和任務,我們接下來實現具體的任務排程
新建SchedulerCenter類 ---------------------------------------------排程中心
public class SchedulerCenter { private readonly IJobFactory _jobFactory; private readonly ISchedulerFactory _schedulerFactory; private IScheduler _scheduler; public SchedulerCenter(IJobFactory jobFactory, ISchedulerFactory schedulerFactory) { _jobFactory = jobFactory; _schedulerFactory = schedulerFactory; } public async void StartScheduler() { //1、從工廠獲取排程程式例項 _scheduler = await _schedulerFactory.GetScheduler(); // 替換預設工廠 //_scheduler.JobFactory = this._jobFactory; //2、開啟排程器 await _scheduler.Start(); //3、定義作業詳細資訊並將其與HelloJob任務相關聯 IJobDetail job = JobBuilder.Create<HelloJob>() .WithIdentity("HelloJob", "HelloJobGroup") .Build(); //4、配置觸發條件:立即觸發作業執行,然後每10秒重複一次 ITrigger trigger = TriggerBuilder.Create() .WithIdentity("HelloJob", "HelloJobGroup") .StartNow() .WithSimpleSchedule(x => x .WithIntervalInSeconds(10) .RepeatForever()) .Build(); //5、將作業與觸發條件新增到排程例項並進行關聯 await _scheduler.ScheduleJob(job, trigger); } public void StopScheduler() { _scheduler?.Shutdown(true).Wait(30000); _scheduler = null; } }
SchedulerCenter 中定義了兩個方法 StartScheduler(開啟排程)和StopScheduler(停止排程)
StartScheduler方法中關於任務配置與官網相同,這裡不再贅述 Quartz.Net官方配置
這裡注掉了一段程式碼 替換預設工廠 ---------我是伏筆②
接下來我們要啟動這個定時任務,在Startup類的ConfigureServices方法中進行注入
//注入排程中心 services.AddSingleton<SchedulerCenter>(); //注入Quartz任何工廠及排程工廠 services.AddSingleton<IJobFactory, CronJobFactory>(); services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>(); //注入HelloJob services.AddTransient<HelloJob>();
在Startup類的Configure方法中進行啟動配置
//獲取排程中心例項 var quartz = app.ApplicationServices.GetRequiredService<SchedulerCenter>(); lifetime.ApplicationStarted.Register(() => { quartz.StartScheduler(); //專案啟動後啟動排程中心 }); lifetime.ApplicationStopped.Register(() => { quartz.StopScheduler(); //專案停止後關閉排程中心 });
執行截圖:
成功啟動定時任務
其他方式啟動定時任務: Net Core 官方使用方式
四、Job實現服務注入
HelloJob類中注入使用者資訊服務,輸出使用者資訊,改動如下:
public class HelloJob : IJob { private readonly IUserInfoServices _userInfoServices; public HelloJob(IUserInfoServices userInfoServices) { _userInfoServices = userInfoServices; } public async Task Execute(IJobExecutionContext context) { var userInfo = _userInfoServices.GetUserInfo(); await Console.Out.WriteLineAsync($"{DateTime.Now:HH:mm:ss}--姓名:{userInfo.UserName},年齡:{userInfo.Age},地址:{userInfo.Address}"); } }
再次執行專案,等待了一段時間發現並沒有我們想要的輸出(這裡折騰我好久。。。)
這時回看剛剛埋下的伏筆① ,IJobFactory介面中有兩個方法,NewJob和ReturnJob,這倆個方法做什麼用?
NewJob方法: 註解的大概意思是:當觸發器觸發時獲取一個Job例項供排程器執行,那麼如何獲取的Job例項呢?
我們看下NewJob第一個引數 TriggerFiredBundle,轉到TriggerFiredBundle定義,看其為我們提供了什麼。
如上圖所示:TriggerFiredBundle中可以獲取到IJobDetail
IJobDetail中有JobType屬性也就是我們定義的Job型別,我們要做的就是獲取到Job例項
安裝 Microsoft.AspNetCore.Antiforgery包
Install-Package Microsoft.AspNetCore.Antiforgery -Version 2.2.0
改動Job工廠類如下所示:
public class CronJobFactory : IJobFactory { private readonly IServiceProvider _serviceProvider; public CronJobFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob; } public void ReturnJob(IJob job) { } }
接著開啟SchedulerCenter中 伏筆② 的註釋:替換預設工廠
// 替換預設工廠 _scheduler.JobFactory = this._jobFactory;
再次執行,使用者服務成功呼叫
猜測:替換預設任務工廠後 IServiceProvider 取到的HelloJob是 ConfigureServices 中使用依賴注入的方式取到的例項,可取到UserInfoServices;
而未替換預設任務工廠取到的HelloJob並不能取到UserInfoServices,故HelloJob的Execute不能執行。