目录
0. 任务调度
1. Quartz .NET
1.1 基本概念
1.2 主要接口和对象
2. 使用示例
2.0 准备工作
2.1 每间隔一定时间间隔执行一次任务
2.3 某天的固定时间点执行任务
2.4 封装整个定时任务, 并给任务传递参数
2.5 关于调度器的一些说明
2.6 关于监视器
参考及示例代码下载
shanzm-2020 年 3 月 25 日 21:28:09
0. 任务调度
比如说, 财务系统需要在每个月初生成上一个月的财务报表.
比如说, 每天或每周固定时间对数据库更新.
比如说, 每天定时发送邮件.
这些需要在某个预定的时间点周期性的执行某个特定的任务的功能 (也就是任务调度), 可以使用任务调度框架 --Quartz .NET
Quartz.NET 是一个开源的任务调度框架 (作业调度框架), 是从 Java 移植过来的, 使用较为广泛!
1. Quartz .NET
1.1 基本概念
调度器 (Scheduler): 存放触发器和定时任务, 根据触发器执行定时任务
触发器 (Trigger): 决定执行时间, 执行间隔, 运行次数, 故触发器用来告诉调度程序作业什么时候触发
任务 (Job): 需要定时或是周期性执行的任务
使用流程:
创建调度器 --> 创建任务 --> 创建触发器 -->Job 和 Trigger 注册到调度器 --> 启动调度器;
1.2 主要接口和对象
接口 / 类 | 作用 |
---|---|
IScheduler | 调度器接口 |
IJob | 任务接口,将需要定时执行的方法实现在该接口的 Excute 方法中 |
IJobDetail | 用于定义 Job 的实例 |
ITrigger | 触发器接口 |
JobBuilder | 任务构造者:用于创建任务实例 |
TriggerBuilder | 触发器构造者:用于创建触发器实例 |
JobDetailImpl | 实现了 IJobDetail 类 |
JobKey | 任务名 |
TriggerKey | 触发器名 |
其中触发器的类型
触发器最常用的主要有两种:
SimpleTrigger: 用于指定任务重复执行的时间间隔
IMutableTrigger: 用于指定任务重复执行的具体时间
2. 使用示例
2.0 准备工作
1安装 Quartz 程序包
当前时间: 2020 年 3 月 18 日 23:20:59, 最新版本的 Quartz.NET 为 3.0.7
每次的版本的变化, API 变化都好大, 所以在这里注明当前的使用版本!
建议使用最新版本, 新版本都是异步方法实现的.
NuGet:Install-Package Quartz -Version 3.0.7
2新建 TestJob.cs
实现 IJob 接口
- public class TestJob : IJob
- {
- public async Task Execute(IJobExecutionContext context)
- {
- await Task.Run(() => Console.WriteLine($"{DateTime.Now}: 执行任务了......"));
- }
- }
2.1 每间隔一定时间间隔执行一次任务
- // 间隔 5s 重复一次执行指定的任务
- public static async void WithInterval()
- {
- //-------1. 准备调度者
- ISchedulerFactory factory = new StdSchedulerFactory(); // 创建调度器工厂
- IScheduler scheduler = await factory.GetScheduler(); // 创建调度者
- //-------2. 准备任务
- JobBuilder jobBuilder = JobBuilder.Create<TestJob>();// 创建任务构造者: JobBuilder
- IJobDetail job1 = jobBuilder.Build();// 创建任务
- //-------3. 准备触发器
- TriggerBuilder triggerBuilder = TriggerBuilder.Create()
- .StartNow()// 立即生效
- .WithSimpleSchedule(x=>x.WithIntervalInSeconds(5)
- .RepeatForever()); // 创建触发器构造者: TriggerBuilder
- ISimpleTrigger trigger = triggerBuilder.WithIdentity("trigger1","group2").Build() as ISimpleTrigger;// 创建触发器
- //-------4. 将任务与触发器添加到调度器中
- await scheduler.ScheduleJob(job1, trigger);
- await scheduler.Start();// 开始执行
- }
[代码说明]
示例中使用的而是 ISimpleTrigger 类型的触发器, 可以精准的设置任务重复的时间间隔.
其中的触发器构造者中的
triggerBuilder.StartNow() 表示触发器立即生效
triggerBuilder.StartAt(DateTimeOffset startTimeUtc) 设置触发器生效的时间
triggerBuilder.EndAt(DateTimeOffset startTimeUtc) 设置触发器失效的时间
其中使用 WithIntervalInSeconds(5) 表示每五秒触发一次任务
其他的一些按照小时和天的做间隔, 以及明确触发次数的方法都简单明确, 根据 VS 的智能提示即可了解, 不一一列举于此!
示例中使用 RepeatForever() 表示重复无穷次, 还是可以使用 WithRepeatCount() 设置重复的次数的.
这里有一个细节问题, 比如说, 设置执行三次, WithRepeatCount(3), 但是注意实际会执行 4 次
2.3 某天的固定时间点执行任务
Quartz.NET 的接口比较繁多, 第一个示例中是使用的最基础的方法, 下面代码示例将换一种简写的方式.
- // 每天按照指定的时间点执行任务
- public static async void AtHourAndMinute()
- {
- // 创建调度器
- IScheduler scheduler = await new StdSchedulerFactory().GetScheduler();
- // 创建任务
- //JobDetailImpl job1 = new JobDetailImpl("TestJob1", "group1", typeo(TestJob))//JobDetailImpl 是 IJobDetail 的实现类
- // 等价于:
- IJobDetail job1 = JobBuilder.Create<TestJob>().WithIdentity("Testjob1""group1").Build();
- // 创建触发器
- IMutableTrigger trigger2job1 = CronScheduleBuilder.DailyAtHourAndMinut(03, 50).Build();// 每天更具某时间点重复触发任务
- // 将任务和触发器添加到调度器中
- trigger2job1.Key = new TriggerKey("trigger1");// 注意一定要给触发器命名
- await scheduler.ScheduleJob(job1, trigger2job1);
- // 开始执行调度者
- await scheduler.Start();
- }
[代码说明]
示例中使用的是 IMutableTrigger 类型的触发器
通过 CronScheduleBuilder 类的静态方法可以设置触发的具体的某一日
设置触发时间为每天的某时某分: DailyAtHourAndMinut(03, 50)
设置触发时间是一周中的哪几天中的几时几分 :AtHourAndMinuteOnGivenDaysOfWeek(int hour , int min, params DayOfWeek[] daysOfWeek)
设置触发时间是每月中某天某时某分 :CronScheduleBuilder.MonthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int min).Build()
封装好的一些方法还是有一定局限的 (但是我自己够用的了), 关于其他的一些复杂的周期任务, 都是可以使用 cron expression, 使用 cron expression 可以定义你能想到的所有触发时间和周期
cron expression 什么样? 怎么用? 例如设置触发的时间是: 每年每月的 2 点 18 分 40 秒 CronScheduleBuilder.CronSchedule("40 18 2 ? * * *").WithIdentity("trigger1").Build();
关于 cron expression 写起来还是有点麻烦的, 可以使用一些在线生成器为我们自动的生成期望的表达式.
推荐: Cron Expression Generator
2.4 封装整个定时任务, 并给任务传递参数
前面的示例为了简洁的表示 Quartz.NET 的一些 API 的使用,
项目中都是把为定时任务, 整个的操作流程封装在一个静态方法中, 存放在我们自定义的 Job 类中
做一个简单的示例: 定时发送短信.
自定义 Job, 实现 IJob 接口, 同时把创建调度器对象, 创建触发器和任务封装于其中, 作为一个静态方法
- class TestJob2 : IJob
- {
- public async Task Execute(IJobExecutionContext context)
- {
- try
- {
- JobDataMap dataMap = context.MergedJobDataMap;
- string tag = dataMap.GetString("tag");
- string title = dataMap.GetString("title");
- string content = dataMap.GetString("content");
- string description = dataMap.GetString("description");
- string tels = dataMap.GetString("tels");
- // 执行定时任务: 模拟发送短信
- await Task.Run(() => Console.WriteLine($"发短信:[{tag}] ,{title}:{content },{description}, 电话:{tels}."));
- //await context.Scheduler.Shutdown();// 表示完成当前的定时任务, 关闭调度器
- // 记入日志
- Console.WriteLine("执行了一次定时任务, 记入日志");
- }
- catch (Exception ex)
- {
- // 记入日志 Log.Error()
- Console.WriteLine(ex.Message);
- }
- }
- // 将创建定时任务的所有操作封装在此
- public static async void SendMessage(string starttime, string cronStr,string tag, string title, string content,string description, string tels)
- {
- try
- {
- // 创建调度器
- IScheduler scheduler = await new StdSchedulerFactory().GetScheduler();
- // 为任务准备参数
- DateTime time = DateTime.Parse(starttime);
- JobDataMap jobData = new JobDataMap()
- {
- new KeyValuePair<string, object>("tag", tag),
- new KeyValuePair<string, object>("title", title),
- new KeyValuePair<string, object>("content", content),
- new KeyValuePair<string, object>("description", description),
- new KeyValuePair<string, object>("tels", tels),
- };
- // 创建任务:
- // 注意可以用时间做组名: DateTime.Now.ToLongDateString()
- IJobDetail job = JobBuilder.Create<TestJob2>()
- .WithIdentity("Testjob1", "group1")
- .SetJobData(jobData)
- .Build();
- // 创建触发器
- ITrigger trigger = TriggerBuilder.Create()
- .WithIdentity("triger1", "group1")
- .StartAt(time)// 触发器开始时间 //.StartNow() 现在开始
- .WithCronSchedule(cronstr)
- .Build();
- // 将任务和触发器添加到调度器中
- await scheduler.ScheduleJob(job, trigger);
- await scheduler.Start();
- }
- catch (Exception ex)
- {
- // 记入日志
- Console.WriteLine(ex.Message);
- }
- }
- }
调用:
- public static async void PackageJob()
- {
- // 从系统当前时间, 每隔 5s, 发送一条短信:[新闻] , 新冠病毒, 治愈者越来越多, 普天同庆, 10086.
- await Task.Run(() => TestJob2.SendMessage(DateTime.Now .ToString(),"/5 * * ? * *","新闻", "新冠病毒", "治愈者越来越多", "普天同庆", "10086"));
- }
[代码说明]
使用 JobDataMap 类型存放需要传递到 IJob 接口的 Excute(IJobExecutionContext context) 方法中
在 JobDataMap 中以键值对的方式存放数据, jobDataMao.Add("key",value)
在定义 Job 的时候, 使用触发器对象中的方法 jobBuilder.SetJobData(jobData) 将 JobDataMap 类型的数据传递到任务中
使用 JobDataMap dataMap = context.MergedJobDataMap; 获取传递到 Excute() 中的 JobDataMap 类型的数据
使用 string value = dataMap.GetString("key"); 获取数据
因为定时任务的是延时的执行的, 所以切记一定要把每个周期中执行的定时任务记入到日志中, 便于维护管理!
注意, 因为实现了 IJob 接口的任务类, 其 Excute() 方法是在一个单独的线程中运行的, 所以其异常的处理也在 Excute() 中使用 try......catch...... 进行处理
BTW: 在 MVC 项目中使用 Quartz .NET, 直接在 Global.asax.cs 中的 Application_Start() 运行封装好的定时任务即可
2.5 关于调度器的一些说明
一个调度器中可以调度多个方法
使用 scheduler.ScheduleJob(job,trigger) 将指定的任务和触发器添加到指定的调度器中, 可以多次添加, 从而实现一个调度器中调度多个任务
但是有一点要注意: 一个任务可以有多个触发器, 但是一个触发器只能对应一个任务
调度器可以添加任务, 那么就一定是可以移除任务的
- // 停止触发器
- await scheduler.PauseTrigger(triggerKey);
- // 移除触发器
- await scheduler.UnscheduleJob(triggerKey);
- // 删除任务
- await scheduler.DeleteJob(jobkey);
调度器可以开始运行, 那么就一定停止运行:
context.Scheduler.Shutdown(); 表示完成当前的定时任务, 关闭调度器
2.6 关于监视器
Undone......
可参考监听器: JobListeners/TriggerListeners/SchedulerListeners
参考及示例代码下载
示例中的源代码下载
官方文档: 官方教程: Quartz.NET 3.x Tutorial
监听器: Quartz.NET 使用教程
Quartz(Java) 源码: Quartz 源码解析 https://www.jianshu.com/p/3f77224ad9d4
远程管理及可视化操作: ASP.NET MVC5 实现基于 Quartz.NET 任务调度
配置方式 1:Quartz.NET 文档 入门教程
配置方式 2:Quartz.NET 定时任务简单实用 (实例)
Cron Expression:Cron Expression Generator
封装任务和传参: NET 作业调度 (定时任务)-Quartz.NET https://www.jianshu.com/p/7a17a64b1ce3
取消任务: Net 作业调度 -----Quartz.NET
来源: https://www.cnblogs.com/shanzhiming/p/12570677.html