C#作业调度Quartz.NET学习笔记
一、简单介绍
Quartz.NET是一个强大、开源、轻量的作业调度框架,是 OpenSymphony 的 Quartz API 的.NET移植,用C#改写,可用于WinForm和ASP.NET应用中。它灵活而不复杂,可以为执行一个作业而创建简单或复杂的作业调度。它有很多特征,如:数据库支持、集群、插件、支持cron-like表达式等等。
官网:http://www.quartz-scheduler.net/
源码:https://github.com/quartznet/quartznet
示例:http://www.quartz-scheduler.net/documentation/quartz-2.x/quick-start.html
二、概念解释
Scheduler:作业调度器。
IJob:作业接口,继承并实现Execute, 编写执行的具体作业逻辑。
JobBuilder:根据设置,生成一个详细作业信息(JobDetail)。
TriggerBuilder:根据规则,生产对应的Trigger。
三、示例程序
3.1、界面
新建一个WinForm程序Client,项目右键->属性->应用程序->输出类型,选择控制台应用程序。
3.2、引用
项目右键->管理 NuGet 程序包->Quartz.NET。
3.2、作业
新建一个类DataSyncJob并继承IJob,代表它是一个作业,同时实现Execute方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Quartz; namespace LinkTo.Test.Quartz.Client { //打上DisallowConcurrentExecution标签让Job进行单线程跑,避免没跑完时的重复执行。 [DisallowConcurrentExecution] public class DataSyncJob : IJob { public Task Execute(IJobExecutionContext context) { JobDataMap keyValuePairs = context.MergedJobDataMap; if (keyValuePairs.Count > 0 && keyValuePairs.Contains("Hello")) { string value = context.MergedJobDataMap.Get("Hello").ToString(); return Task.Run(() => { Console.WriteLine(DateTime.Now + $" Hello:{value}" + Environment.NewLine); }); } else { return Task.Run(() => { Console.WriteLine(DateTime.Now + Environment.NewLine); }); } } } }
说明:
1)一般来说,作业需打上[DisallowConcurrentExecution]标签,以避免当次作业尚未完成时又被开始调度执行。
2)作业可以接收触发器传递过来的参数(Key-Value),上面作业接收的是"Hello"参数。
3.3、调度
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Quartz; using Quartz.Impl; namespace LinkTo.Test.Quartz.Client { public partial class Main : Form { //调度器工厂 private ISchedulerFactory factory; //调度器 private IScheduler scheduler; public Main() { InitializeComponent(); //按钮状态 btnStart.Enabled = true; btnStop.Enabled = false; } /// <summary> /// 开始 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnStart_Click(object sender, EventArgs e) { //1、创建一个调度器 factory = new StdSchedulerFactory(); scheduler = await factory.GetScheduler(); await scheduler.Start(); //2、创建一个任务 IJobDetail job = JobBuilder.Create<DataSyncJob>().WithIdentity("DataSync", "DataSync_Group").Build(); //3、创建一个触发器(有4种触发器供选择) //4、将任务与触发器添加到调度器中 #region 触发器1:WithSimpleSchedule //ITrigger simpleTrigger = TriggerBuilder.Create() // .WithIdentity("DataSync_SimpleTrigger", "DataSync_TriggerGroup") // .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()) // .Build(); //await scheduler.ScheduleJob(job, simpleTrigger); #endregion #region 触发器2:WithDailyTimeIntervalSchedule //ITrigger dailyTimeTrigger = TriggerBuilder.Create() // .WithIdentity("DataSync_DailyTimeTrigger", "DataSync_TriggerGroup") // .WithDailyTimeIntervalSchedule(x => x.OnEveryDay().WithIntervalInSeconds(5)) // .Build(); //await scheduler.ScheduleJob(job, dailyTimeTrigger); #endregion #region 触发器3:WithCalendarIntervalSchedule //ITrigger calendarTrigger = TriggerBuilder.Create() // .WithIdentity("DataSync_CalendarTrigger", "DataSync_TriggerGroup") // .WithCalendarIntervalSchedule(x => x.WithIntervalInSeconds(5)) // .Build(); //await scheduler.ScheduleJob(job, calendarTrigger); #endregion #region 触发器4:WithCronSchedule(带传递参数"Hello") ITrigger cronTrigger = TriggerBuilder.Create() .WithIdentity("DataSync_CronTrigger", "DataSync_TriggerGroup") .WithCronSchedule("0/5 * * * * ?") .UsingJobData("Hello", Guid.NewGuid().ToString()) .Build(); await scheduler.ScheduleJob(job, cronTrigger); #endregion //5、开始执行 await scheduler.Start(); //按钮状态 btnStart.Enabled = false; btnStop.Enabled = true; } /// <summary> /// 停止 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStop_Click(object sender, EventArgs e) { if (scheduler != null) { scheduler.Shutdown(true); } //按钮状态 btnStart.Enabled = true; btnStop.Enabled = false; } } }
说明:
1)Job及Trigger在WithIdentity时都会用到**Group,此处Group的作用是用于分类,相当于一个命名空间。
2)触发器有4种,分别是WithSimpleSchedule、WithDailyTimeIntervalSchedule、WithCalendarIntervalSchedule、WithCronSchedule,常用第1种及第4种。
3)WithCronSchedule触发器使用的是Cron表达式。
3.4、结果
四、Cron表达式
1)在线Cron表达式生成器:https://cron.qqe2.com/
2)官方英文介绍:https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/crontrigger.html
3)Cron表达式整体上还是比较容易理解的,只有一点需要注意"?"号的用法。"?"可以用在 Day of Month 和 Day of Week 中,比如:每月1号的每小时的第31分钟,正确的表达式是:* 31 * 1 * ?,而不能是:* 31 * 1 * *,因为这样代表的是每周的任意一天。
Cron表达式由7段构成:秒 分 时 日 月 星期 年(可选)
"-":表示范围,MON-WED表示星期一到星期三。
",":表示列举,MON,WEB表示星期一和星期三。
"*":表示"每",每天、每月、每周、每年等。
"/":表示增量,0/15(分钟)表示每15分钟,在0分以后开始;3/20表示每20分钟,从3分钟以后开始。
"?":只能出现在日、星期段里面,表示不指定具体的值。
"L":只能出现在日、星期段里面,是Last的缩写,表示如一个月的最后一天、一个星期的最后一天(星期六)。
"W":表示工作日,距离给定值最近的工作日。
"#":表示一个月的第几个星期几,"6#3"表示每个月的第三个星期五(1=SUN...6=FRI,7=SAT)。
4)官方示例:
表达式 | 解释 |
---|---|
0 0 12 * * ? | 每天中午12点触发 |
0 15 10 ? * * | 每天上午10:15触发 |
0 15 10 * * ? | 每天上午10:15触发 |
0 15 10 * * ? * | 每天上午10:15触发 |
0 15 10 * * ? 2005 | 2005年的每天上午10:15触发 |
0 * 14 * * ? | 在每天下午2点到下午2:59期间的每1分钟触发 |
0 0/5 14 * * ? | 在每天下午2点到下午2:55期间的每5分钟触发 |
0 0/5 14,18 * * ? | 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 |
0 0-5 14 * * ? | 在每天下午2点到下午2:05期间的每1分钟触发 |
0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44触发 |
0 15 10 ? * MON-FRI | 周一至周五的上午10:15触发 |
0 15 10 15 * ? | 每月15日上午10:15触发 |
0 15 10 L * ? | 每月最后一日的上午10:15触发 |
0 15 10 L-2 * ? | 每个月的第二天到最后一天的上午10:15触发 |
0 15 10 ? * 6L | 每月的最后一个星期五上午10:15触发 |
0 15 10 ? * 6L | 每个月最后一个星期五上午10时15分触发 |
0 15 10 ? * 6L 2002-2005 | 2002年至2005年的每月的最后一个星期五上午10:15触发 |
0 15 10 ? * 6#3 | 每月的第三个星期五上午10:15触发 |
0 0 12 1/5 * ? | 每月每隔5天下午12点(中午)触发, 从每月的第一天开始 |
0 11 11 11 11 ? | 每11月11日上午11时11分触发 |
五、一行代码实现调度
新建一个TestJob:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Quartz; namespace LinkTo.Test.Quartz.Client { //打上DisallowConcurrentExecution标签让Job进行单线程跑,避免没跑完时的重复执行。 [DisallowConcurrentExecution] public class TestJob : IJob { public Task Execute(IJobExecutionContext context) { return Task.Run(() => { Console.WriteLine(DateTime.Now + Environment.NewLine); }); } } }
新建一个调度封装类QuartzFactory:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Quartz; using Quartz.Impl; using Quartz.Impl.Triggers; namespace LinkTo.Test.Quartz.Client { public class QuartzFactory { //调度器工厂 private static ISchedulerFactory factory = null; //调度器 private static IScheduler scheduler = null; /// <summary> /// 构造函数 /// </summary> static QuartzFactory() { factory = new StdSchedulerFactory(); scheduler = factory.GetScheduler().Result; scheduler.Start(); } #region 触发器1:添加Job /// <summary> /// 触发器1:添加Job并以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jobName"></param> /// <param name="startTime"></param> /// <param name="simpleTime"></param> /// <param name="jobDataMap"></param> /// <returns></returns> public static DateTimeOffset AddJob<T>(string jobName, DateTimeOffset startTime, TimeSpan simpleTime, Dictionary<string, object> jobDataMap) where T : IJob { IJobDetail jobCheck = JobBuilder.Create<T>().WithIdentity(jobName, jobName + "_Group").Build(); jobCheck.JobDataMap.PutAll(jobDataMap); ISimpleTrigger triggerCheck = new SimpleTriggerImpl(jobName + "_SimpleTrigger", jobName + "_TriggerGroup", startTime, null, SimpleTriggerImpl.RepeatIndefinitely, simpleTime); return scheduler.ScheduleJob(jobCheck, triggerCheck).Result; } /// <summary> /// 触发器1:添加Job并以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jobName"></param> /// <param name="startTime"></param> /// <param name="simpleTime">毫秒数</param> /// <param name="mapKey"></param> /// <param name="mapValue"></param> /// <returns></returns> public static DateTimeOffset AddJob<T>(string jobName, DateTimeOffset startTime, int simpleTime, string mapKey, object mapValue) where T : IJob { Dictionary<string, object> jobDataMap = new Dictionary<string, object> { { mapKey, mapValue } }; return AddJob<T>(jobName, startTime, TimeSpan.FromMilliseconds(simpleTime), jobDataMap); } /// <summary> /// 触发器1:添加Job并以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jobName"></param> /// <param name="startTime"></param> /// <param name="simpleTime"></param> /// <returns></returns> public static DateTimeOffset AddJob<T>(string jobName, DateTimeOffset startTime, TimeSpan simpleTime) where T : IJob { return AddJob<T>(jobName, startTime, simpleTime, new Dictionary<string, object>()); } /// <summary> /// 触发器1:添加Job并以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jobName"></param> /// <param name="startTime"></param> /// <param name="simpleTime">毫秒数</param> /// <returns></returns> public static DateTimeOffset AddJob<T>(string jobName, DateTimeOffset startTime, int simpleTime) where T : IJob { return AddJob<T>(jobName, startTime, TimeSpan.FromMilliseconds(simpleTime)); } /// <summary> /// 触发器1:添加Job并以周期的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jobName"></param> /// <param name="simpleTime">毫秒数</param> /// <returns></returns> public static DateTimeOffset AddJob<T>(string jobName, int simpleTime) where T : IJob { return AddJob<T>(jobName, DateTime.UtcNow.AddMilliseconds(1), TimeSpan.FromMilliseconds(simpleTime)); } #endregion #region 触发器4:添加Job /// <summary> /// 触发器4:添加Job并以定点的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jobName"></param> /// <param name="cronTime"></param> /// <param name="jobDataMap"></param> /// <returns></returns> public static DateTimeOffset AddJob<T>(string jobName, string cronTime, string jobData) where T : IJob { IJobDetail jobCheck = JobBuilder.Create<T>().WithIdentity(jobName, jobName + "_Group").UsingJobData("jobData", jobData).Build(); ICronTrigger cronTrigger = new CronTriggerImpl(jobName + "_CronTrigger", jobName + "_TriggerGroup", cronTime); return scheduler.ScheduleJob(jobCheck, cronTrigger).Result; } /// <summary> /// 触发器4:添加Job并以定点的形式运行 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jobName"></param> /// <param name="cronTime"></param> /// <returns></returns> public static DateTimeOffset AddJob<T>(string jobName, string cronTime) where T : IJob { return AddJob<T>(jobName, cronTime, null); } #endregion /// <summary> /// 修改触发器时间重载 /// </summary> /// <param name="jobName">Job名称</param> /// <param name="timeSpan">TimeSpan</param> /// </summary> public static void UpdateTime(string jobName, TimeSpan simpleTimeSpan) { TriggerKey triggerKey = new TriggerKey(jobName + "_SimpleTrigger", jobName + "_TriggerGroup"); SimpleTriggerImpl simpleTriggerImpl = scheduler.GetTrigger(triggerKey).Result as SimpleTriggerImpl; simpleTriggerImpl.RepeatInterval = simpleTimeSpan; scheduler.RescheduleJob(triggerKey, simpleTriggerImpl); } /// <summary> /// 修改触发器时间重载 /// </summary> /// <param name="jobName">Job名称</param> /// <param name="simpleTime">分钟数</param> /// <summary> public static void UpdateTime(string jobName, int simpleTime) { UpdateTime(jobName, TimeSpan.FromMinutes(simpleTime)); } /// <summary> /// 修改触发器时间重载 /// </summary> /// <param name="jobName">Job名称</param> /// <param name="cronTime">Cron表达式</param> public static void UpdateTime(string jobName, string cronTime) { TriggerKey triggerKey = new TriggerKey(jobName + "_CronTrigger", jobName + "_TriggerGroup"); CronTriggerImpl cronTriggerImpl = scheduler.GetTrigger(triggerKey).Result as CronTriggerImpl; cronTriggerImpl.CronExpression = new CronExpression(cronTime); scheduler.RescheduleJob(triggerKey, cronTriggerImpl); } /// <summary> /// 暂停所有Job /// </summary> public static void PauseAll() { scheduler.PauseAll(); } /// <summary> /// 恢复所有Job /// </summary> public static void ResumeAll() { scheduler.ResumeAll(); } /// <summary> /// 删除指定Job /// </summary> /// <param name="jobName"></param> public static void DeleteJob(string jobName) { JobKey jobKey = new JobKey(jobName, jobName + "_Group"); scheduler.DeleteJob(jobKey); } /// <summary> /// 卸载定时器 /// </summary> /// <param name="isWaitForToComplete">是否等待Job执行完成</param> public static void Shutdown(bool isWaitForToComplete) { scheduler.Shutdown(isWaitForToComplete); } } }
一行代码实现作业调度按钮代码:
/// <summary> /// 一行代码实现作业调度 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnOneCode_Click(object sender, EventArgs e) { if (btnOneCode.Text == "一行代码实现作业调度") { string cronTime = "0/5 * * * * ?"; QuartzFactory.AddJob<TestJob>("TestJob", cronTime); btnOneCode.Text = "Stop"; } else { QuartzFactory.DeleteJob("TestJob"); btnOneCode.Text = "一行代码实现作业调度"; } }
参考自: