十年河東,十年河西,莫欺少年窮
學無止境,精益求精
1、概述
Quartz.Net是根據Java的Quartz用C#改寫而來,Quartz.NET是一個開源的作業調度框架,非常適合在平時的工作中,定時輪詢數據庫同步,定時郵件通知,定時處理數據等。 Quartz.NET允許開發人員根據時間間隔來調度作業。它有很多特徵如:數據庫支持,集羣,插件,支持cron-like表達式等等。
2、參考
Quartz.Net源碼:https://github.com/quartznet/quartznet
官方學習文檔:http://www.quartz-scheduler.net/documentation/index.html
3、Quartz.Net說明
Quartz主要有三部分組成任務(Job)、觸發器(Trigger)和調度器(Schedule)。
3.1、作業
Job就是執行的作業,Job需要繼承IJob接口,實現Execute方法。Job中執行的參數從Execute方法的參數中獲取。
3.2、觸發器
觸發器常用的有兩種:SimpleTrigger觸發器和CronTrigger觸發器。
SimpleTrigger
實現簡單業務,如每隔幾分鐘,幾小時觸發執行,並限制執行次數。
- 重複執行:WithRepeatCount()/RepeatForever()
- 設置間隔時間:WithInterval()
- 定時執行:StartAt()/StartNow()
- 設定優先級:WithPriority(),默認爲5
var trigger = TriggerBuilder.Create() .WithSimpleSchedule(x => x.WithIntervalInSeconds(2).WithRepeatCount(5))//間隔2秒 執行6次 .UsingJobData("key1", 321) .WithIdentity("trigger", "group") .Build();
CronTrigger
CornTrigger需要接合Corn表達式
常用cron表達式
- */10 * * * * ? 每隔10秒執行一次
- 0 */5 * * * ? 每隔5分鐘執行一次
- 0 2,22,32 * * * ? 在2分、22分、32分執行一次
- 0 0 4-8 * * ? 每天4-8點整點執行一次
- 0 0 2 * * ? 每天凌晨2點執行一次
- 0 0 2 1 * ? 每月1號凌晨2點執行一次
cron表達式生成器
現在也有許多在線的cron表達式生成器:
https://www.toolzl.com/tools/croncreate.html
var trigger = TriggerBuilder.Create().StartNow() .WithCronSchedule("0/2 * * * * ? *")//每兩秒執行一次 .Build();
3.3、調度器
調度器就是將任務和觸發器綁定,讓觸發器觸發的時候去執行任務。
3.4、參數說明
-
SetJobData:設置JobData
-
StoreDurably:孤立存儲,指即使該JobDetail沒有關聯的Trigger,也會進行存儲
-
RequestRecovery:請求恢復,指應用崩潰後再次啓動,會重新執行該作業
-
WithIdentity:作業的唯一標識
-
WithDescription:作業的描述信息
除此之外,Quartz.Net
還支持兩個非常有用的特性:
-
DisallowConcurrentExecution:禁止並行執行,該特性是針對JobDetail生效的
-
PersistJobDataAfterExecution:在執行完成後持久化JobData,該特性是針對Job類型生效的,意味着所有使用該Job的JobDetail都會在執行完成後持久化JobData。
4、新建控制檯應用程序
項目清單如下
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Quartz.AspNetCore" Version="3.5.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" /> <PackageReference Include="NLog.Extensions.Logging" Version="5.0.4" /> </ItemGroup> <ItemGroup> <None Update="appsettings.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup> </Project>
4.1、簡單時間間隔作業
using Newtonsoft.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NLog.Extensions.Logging; using Quartz; using Quartz.Impl; using Quartz.Logging; using System; namespace BatteryService // Note: actual namespace depends on the project name. { internal class Program { static async Task Main(string[] args) { ConfigurationBuilder builder = new ConfigurationBuilder(); builder.AddJsonFile("appsettings.json", true, true); var ConfigRoot = builder.Build();//根節點 IServiceCollection Services = new ServiceCollection(); // Services.AddLogging(log => { log.AddConsole(); log.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information); }); Services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();//註冊ISchedulerFactory的實例。 Services.AddScoped<QuartzService>();//註冊 QuartzService 的實例。 using (ServiceProvider provider = Services.BuildServiceProvider()) { var service = provider.GetService<QuartzService>(); await service.with2Seconds(); }; CreateHostBuilder(args).Run(); } public static IHost CreateHostBuilder(string[] args) { var builder = Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { //services.AddHostedService<IotService>(); }).UseWindowsService(); var host = builder.Build(); return host; } } public class QuartzService { private readonly ISchedulerFactory _schedulerFactory; private IScheduler _scheduler; private readonly ILogger<QuartzService> logger; public QuartzService(ISchedulerFactory schedulerFactory, ILogger<QuartzService> logger) { _schedulerFactory = schedulerFactory; this.logger = logger; } public async Task with2Seconds() { //通過調度工廠獲得調度器 _scheduler = await _schedulerFactory.GetScheduler(); //開啓調度器 await _scheduler.Start(); logger.LogInformation("調度器開始工作"); //創建一個觸發器 var trigger = TriggerBuilder.Create().StartNow() .WithSimpleSchedule(x => x.WithIntervalInSeconds(2).RepeatForever())//每兩秒執行一次 .Build(); //創建任務 var jobDetail = JobBuilder.Create<MyJob>() .WithIdentity("job1", "group") .Build(); //將觸發器和任務器綁定到調度器中 await _scheduler.ScheduleJob(jobDetail, trigger); } } [DisallowConcurrentExecution]//禁止並行執行,該特性是針對JobDetail生效的 public class MyJob : IJob { public Task Execute(IJobExecutionContext context) { return Task.Run(() => { Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); }); } } }
4.2、可以傳遞參數的簡單作業(使用了特性【DisallowConcurrentExecution】防併發作業)
using Newtonsoft.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NLog.Extensions.Logging; using Quartz; using Quartz.Impl; using Quartz.Logging; using System; namespace BatteryService // Note: actual namespace depends on the project name. { internal class Program { static async Task Main(string[] args) { ConfigurationBuilder builder = new ConfigurationBuilder(); builder.AddJsonFile("appsettings.json", true, true); var ConfigRoot = builder.Build();//根節點 IServiceCollection Services = new ServiceCollection(); // Services.AddLogging(log => { log.AddConsole(); log.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information); }); Services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();//註冊ISchedulerFactory的實例。 Services.AddScoped<QuartzService>();//註冊 QuartzService 的實例。 using (ServiceProvider provider = Services.BuildServiceProvider()) { var service = provider.GetService<QuartzService>(); await service.with2Seconds(); }; CreateHostBuilder(args).Run(); } public static IHost CreateHostBuilder(string[] args) { var builder = Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { //services.AddHostedService<IotService>(); }).UseWindowsService(); var host = builder.Build(); return host; } } public class QuartzService { private readonly ISchedulerFactory _schedulerFactory; private IScheduler _scheduler; private readonly ILogger<QuartzService> logger; public QuartzService(ISchedulerFactory schedulerFactory, ILogger<QuartzService> logger) { _schedulerFactory = schedulerFactory; this.logger = logger; } public async Task with2Seconds() { //通過調度工廠獲得調度器 _scheduler = await _schedulerFactory.GetScheduler(); //開啓調度器 await _scheduler.Start(); logger.LogInformation("調度器開始工作"); //創建一個觸發器 var trigger = TriggerBuilder.Create().StartNow().UsingJobData("Trigger", "我是在Trigger中設置的參數").UsingJobData("TriggerParmCount", 1) .WithSimpleSchedule(x => x.WithIntervalInSeconds(2).WithRepeatCount(1))//每兩秒執行一次 .Build(); //創建任務 var jobDetail = JobBuilder.Create<MyJob>() .UsingJobData("Job_1", "我是在Job中設置的參數") .UsingJobData("Job_2", "我是在Job中設置的參數") .UsingJobData("ParmCount", 2) .WithIdentity("jobName", "group") .Build(); //將觸發器和任務器綁定到調度器中 await _scheduler.ScheduleJob(jobDetail, trigger); } } [DisallowConcurrentExecution]//禁止並行執行,該特性是針對JobDetail生效的 public class MyJob : IJob { public Task Execute(IJobExecutionContext context) { var jobData = context.JobDetail.JobDataMap;//獲取Job中的參數 var triggerData = context.Trigger.JobDataMap;//獲取Trigger中的參數 var allData = context.MergedJobDataMap;//獲取Job和Trigger中合併的參數 var Trigger = triggerData.GetString("Trigger"); int TriggerParmCount = triggerData.GetInt("TriggerParmCount"); var Job_1 = triggerData.GetString("Job_1"); var Job_2 = triggerData.GetString("Job_2"); int ParmCount = triggerData.GetInt("ParmCount"); return Task.Run(() => { foreach (var item in jobData) { Console.WriteLine($"Job中的參數,鍵爲:{item.Key}值爲:{item.Value}"); } Console.WriteLine("-----------------------------------------------"); foreach (var item in triggerData) { Console.WriteLine($"Trigger中的參數,鍵爲:{item.Key}值爲:{item.Value}"); } Console.WriteLine("-----------------------------------------------"); foreach (var item in allData) { Console.WriteLine($"Job和Trigger中的所有參數,鍵爲:{item.Key}值爲:{item.Value}"); } }); } } }
4.3、可變參數簡單作業(使用了特性【PersistJobDataAfterExecution】存儲JobDataMap副本)
using Newtonsoft.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NLog.Extensions.Logging; using Quartz; using Quartz.Impl; using Quartz.Logging; using System; namespace BatteryService // Note: actual namespace depends on the project name. { internal class Program { static async Task Main(string[] args) { ConfigurationBuilder builder = new ConfigurationBuilder(); builder.AddJsonFile("appsettings.json", true, true); var ConfigRoot = builder.Build();//根節點 IServiceCollection Services = new ServiceCollection(); // Services.AddLogging(log => { log.AddConsole(); log.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information); }); Services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();//註冊ISchedulerFactory的實例。 Services.AddScoped<QuartzService>();//註冊 QuartzService 的實例。 using (ServiceProvider provider = Services.BuildServiceProvider()) { var service = provider.GetService<QuartzService>(); await service.with2Seconds(); }; CreateHostBuilder(args).Run(); } public static IHost CreateHostBuilder(string[] args) { var builder = Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { //services.AddHostedService<IotService>(); }).UseWindowsService(); var host = builder.Build(); return host; } } public class QuartzService { private readonly ISchedulerFactory _schedulerFactory; private IScheduler _scheduler; private readonly ILogger<QuartzService> logger; public QuartzService(ISchedulerFactory schedulerFactory, ILogger<QuartzService> logger) { _schedulerFactory = schedulerFactory; this.logger = logger; } public async Task with2Seconds() { //通過調度工廠獲得調度器 _scheduler = await _schedulerFactory.GetScheduler(); //開啓調度器 await _scheduler.Start(); logger.LogInformation("調度器開始工作"); //創建一個觸發器 var trigger = TriggerBuilder.Create().StartNow() .UsingJobData("name", "陳臥龍") .UsingJobData("sex", "男") .WithSimpleSchedule(x => x.WithIntervalInSeconds(2).WithRepeatCount(9))//每兩秒執行一次 共執行10次 .Build(); //創建任務 var jobDetail = JobBuilder.Create<MyJob>() .UsingJobData("age", 28) .UsingJobData("weight", 70) .WithIdentity("jobName", "group") .Build(); //將觸發器和任務器綁定到調度器中 await _scheduler.ScheduleJob(jobDetail, trigger); } } [PersistJobDataAfterExecution]// 更新JobDetail的JobDataMap的存儲副本,以便下一次執行這個任務接收更新的值而不是原始存儲的值 public class MyJob : IJob { public Task Execute(IJobExecutionContext context) { var allData = context.MergedJobDataMap;//獲取Job和Trigger中合併的參數 var name = allData.GetString("name"); var sex = allData.GetString("sex"); var age = allData.GetInt("age"); var weight = allData.GetInt("weight"); Console.WriteLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); Console.WriteLine($"原始參數爲,姓名:{name},性別:{sex},年齡:{age},體重:{weight}"); allData["name"] = name + "_" + new Random().Next(1, 10); allData["sex"] = sex + "_" + new Random().Next(1, 10); allData["age"] = new Random().Next(20, 100); allData["weight"] = new Random().Next(60, 100); return Task.Run(() => { foreach (var item in allData) { Console.WriteLine($"改變後的參數,鍵爲:{item.Key}值爲:{item.Value}"); } Console.WriteLine("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); }); } } }
4.4、Corn表達式作業調度
corn表示式和簡單調度的區別是:一個使用固定時間間隔作業,比較簡單。一個可以通過corn表達式進行復雜的時間場景調度
現在也有許多在線的cron表達式生成器:
https://www.toolzl.com/tools/croncreate.html
每月最後一天23:59分執行
日部分截圖
小時部分截圖
分鐘部分截圖
秒部分截圖
最終生成的Corn 表達式爲:0 59 23 L * ? *
示例代碼如下:
public async Task with2Seconds() { //通過調度工廠獲得調度器 _scheduler = await _schedulerFactory.GetScheduler(); //開啓調度器 await _scheduler.Start(); logger.LogInformation("調度器開始工作"); //創建一個觸發器 var trigger = TriggerBuilder.Create().StartNow().WithCronSchedule("0 59 23 L * ? *") //每月月底23.59分執行 .UsingJobData("name", "陳臥龍") .UsingJobData("sex", "男") .Build(); //創建任務 var jobDetail = JobBuilder.Create<MyJob>() .UsingJobData("age", 28) .UsingJobData("weight", 70) .WithIdentity("jobName", "group") .Build(); //將觸發器和任務器綁定到調度器中 await _scheduler.ScheduleJob(jobDetail, trigger); }
核心配置:
var trigger = TriggerBuilder.Create().StartNow().WithCronSchedule("0 59 23 L * ? *") //每月月底23.59分執行 .UsingJobData("name", "陳臥龍") .UsingJobData("sex", "男") .Build();
@天才臥龍的博科人、