介紹
Quartz.Net是一個強大、開源、輕量的作業調度框架。在平時開發中主要用於一些定時任務開發,譬如定時發送右鍵,定時同步第三方數據等等。
github:https://github.com/quartznet/quartznet
官網:https://www.quartz-scheduler.net/documentation/quartz-3.x/quick-start.html
目標
這次博客主要實現任務的啟動,暫停,恢復。
封裝
為了項目的低耦合,在封裝Quartz的時候將它單獨封裝在一個類庫中,便於管理。
先創建一個類庫,定義好類庫名字。
安裝nuget包。
Job工廠
在quartz中,在實現Job的時候會實例化一個JobFactory,翻譯過來就是Job工廠,通過查看源碼我找到了一個SimpleJobFactory,這是它的默認實現,它做的事情主要是實例化實現IJob的類。
那么自定義一個自己適用的Job工廠。
在這里的想法是,在IOC中放入Job,然后再工廠中獲取容器中的Job對象。事實上,你實現工廠的核心就是定義IJob實現類的實例化規則!
因為我需要對.net core ioc進行操作,所以安裝一個Microsoft.Extensions.DependencyInjection nuget包
代碼:
/// <summary> /// 自定義job工廠 /// https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/miscellaneous-features.html#plug-ins /// </summary> public class ZeroJobFactory : IJobFactory { readonly IServiceProvider _provider; public ZeroJobFactory(IServiceProvider provider) { _provider = provider; } public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) { try { //從Quartz.net的源碼實現net core注入這一塊能夠發現,job實例是通過AddTransient加入容器中的 //還有自定義的JobFactory也需要單例注入,我覺的是因為如果不單例注入會導致Quartz使用默認的SimpleJobFactory //從而導致這里的獲取Job實例出問題。 var service = _provider.CreateScope(); var job = service.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob; return job; } catch (Exception ex) { throw ex; } } /// <summary> /// 允許工廠釋放Job /// </summary> /// <param name="job"></param> public void ReturnJob(IJob job) { var disposable = job as IDisposable; disposable?.Dispose(); }
創建Job
這個類必須要繼承IJob,否則是沒用的。
[DisallowConcurrentExecution] [PersistJobDataAfterExecution] public class FirstJob : IJob { public async Task Execute(IJobExecutionContext context) { await Task.Run(() => { Console.WriteLine("First Job"); //job詳情 var jobDetails = context.JobDetail; //觸發器的信息 var trigger = context.Trigger; Console.WriteLine($"JobKey:{jobDetails.Key},Group:{jobDetails.Key.Group}\r\n" + $"Trigger:{trigger.Key}\r\n" + $"RunTime:{context.JobRunTime}" + $"ExecuteTime:{DateTime.Now}"); }); } }
控制中心
在自定義Job工廠和Job之后,那如何統一控制呢,這里說到控制中心,在控制中心里實現Job的運行,暫停,恢復等功能。
首先定義一個抽象的任務調度的控制中心,在.net core大多數時候都是面向抽象編程,所以這個控制中心定義為抽象的,便於依賴注入從而能夠方便的在API中進行任務調度。
/// <summary> /// quartz 抽象調度中心 /// </summary> public interface IControllerCenter { /// <summary> /// 啟動任務調度 /// </summary> /// <returns></returns> Task Start(); /// <summary> /// 運行Job /// </summary> /// <returns></returns> Task RunJob(); /// <summary> /// 暫停Job /// </summary> /// <returns></returns> Task PauseJob(); /// <summary> /// 回復job /// </summary> /// <returns></returns> Task ResumeJob(); }
實現任務中心
/// <summary> /// Quartz任務調度控制中心 /// </summary> public class ControllerCenter : IControllerCenter { /// <summary> /// 構造函數注入自定義Job工廠 /// </summary> readonly IJobFactory _jobFactory; private Task<IScheduler> _scheduler; public Task<IScheduler> Center => _scheduler ?? throw new ArgumentNullException("Schedule can not null"); private IScheduler Scheduler => _scheduler.Result; public ControllerCenter(IJobFactory factory) { _jobFactory = factory; _scheduler = GetScheduler(); _scheduler.Result.JobFactory = _jobFactory; } private Task<IScheduler> GetScheduler() { if (_scheduler != null) return _scheduler; else { /* * 配置二進制策略 *https://www.quartz-scheduler.net/documentation/quartz-3.x/migration-guide.html#packaging-changes */ var properties = new NameValueCollection { ["quartz.serializer.type"] = "binary" }; //實例化工廠 ISchedulerFactory sf = new StdSchedulerFactory(properties); this._scheduler = sf.GetScheduler(); return _scheduler; } } public async Task Start() { try { if (this.Scheduler.IsStarted) { Console.WriteLine("quartz is started"); } else { Console.WriteLine("quartz start!"); await Scheduler.Start(); } } catch (Exception ex) { Console.WriteLine(ex.Message); } } public async Task RunJob() { var jobKey = new JobKey("job1", "group1"); if (await Scheduler.CheckExists(jobKey)) { Console.WriteLine("JobKey Exists"); } else { Console.WriteLine("JobKey Allow"); if (!this.Scheduler.IsStarted) { Console.WriteLine("quartz is not started"); await this.Start(); } var job = JobBuilder.Create<FirstJob>() .WithIdentity("job1", "group1") .Build(); var trigger = TriggerBuilder.Create() .WithIdentity("trigger1", "group1") .StartNow() .WithSimpleSchedule(a => { a.RepeatForever();//永遠執行 a.WithIntervalInSeconds(3);//執行間隔3s }) .ForJob(job) .Build(); await Scheduler.ScheduleJob(job, trigger); } } public async Task PauseJob() { var jobKey = new JobKey("job1", "group1"); if (await Scheduler.CheckExists(jobKey)) { await Scheduler.PauseJob(jobKey); Console.WriteLine("PauseJob Success!"); } else { Console.WriteLine("Not IsExists JobKey"); } } public async Task ResumeJob() { var jobKey = new JobKey("job1", "group1"); if (await Scheduler.CheckExists(jobKey)) { await Scheduler.ResumeJob(jobKey); Console.WriteLine("ResumeJob Success!"); } else { Console.WriteLine("Not IsExists JobKey"); } }
可以看到我在RunJob中已經將創建的Job放入,接下來就要說到如何通過控制中心去使用了。
使用
在.net core Startup中將自定義工廠,控制中心放入容器。
//自定義Job工廠 services.AddSingleton<IJobFactory, ZeroJobFactory>(); //任務調度控制中心 services.AddSingleton<IControllerCenter, ControllerCenter>(); //Jobs,將組件好的Job放在這里,生命周期為瞬時的 services.AddTransient<FirstJob>();
創建一個控制器,直接上代碼
[Route("api/[controller]")] [ApiController] public class QuartzController : ControllerBase { readonly IControllerCenter _center; public QuartzController(IControllerCenter center) { _center = center; } /// <summary> /// 開啟任務調度 /// </summary> /// <returns></returns> [HttpGet("Start")] public async Task<JsonResult> Start() { await _center.Start(); return AjaxHelper.Seed(Ajax.Ok); } /// <summary> /// 運行Job /// </summary> /// <returns></returns> [HttpGet("Run")] public async Task<JsonResult> Run() { await _center.RunJob(); return AjaxHelper.Seed(Ajax.Ok); } /// <summary> /// 暫停Job /// </summary> /// <returns></returns> [HttpGet("PauseJob")] public async Task<JsonResult> PauseJob() { await _center.PauseJob(); return AjaxHelper.Seed(Ajax.Ok); } /// <summary> /// 恢復Job /// </summary> /// <returns></returns> [HttpGet("ResumeJob")] public async Task<JsonResult> ResumeJob() { await _center.ResumeJob(); return AjaxHelper.Seed(Ajax.Ok); } }
代碼寫好,就來測試一下。
測試
首先測試啟動任務調度和運行Job
注意看控制台的打印的信息,跟我們在Job中定義的內容是一樣的,而且我在觸發器中指定了沒3s執行一次,可以從執行時間看到是實現了的。
既然任務啟動了,那我就試着將它暫停下吧。
可以從打印信息看出,我在暫停之后又將它恢復使用。這里需要注意的一點是,如果Job沒有啟動是沒法根據JobKey去暫停的。
總結
今天對Quartz結合.net core進行了簡單的配合使用。完成了博客開頭說到的目標:啟動,暫停,恢復。
總的來說Quartz是一個非常好玩的東西的,但是我在動手之前是花了近乎一周的時間去看文檔,看代碼,看優秀.neter對它的使用方式。
在了解到足夠多的信息才去動手做的。這一點的做法對我的幫助是非常大,在做的過程中遇到的阻礙相較來說少了很多。
源碼地址:https://github.com/QQ2287991080/Zero.Core