Quartz.NET 使用
任务调用系统
https://www.cnblogs.com/taadis/p/quartz-3-x-tutorial-index.html
// 1. 实现一个 job
// 2. 定义一个 trigger ,用来指定触发时间及频率
// 3. 启动一个 Scheduler ,将 job 与 trigger 传入进行工作
public async static Task Main(string[] args)
{
// 1.准备调度者
var factory = new StdSchedulerFactory(); // 创建调度器工厂,或是 StdSchedulerFactory.GetDefaultScheduler()
var scheduler = await factory.GetScheduler().ConfigureAwait(false); // 创建调度者
await scheduler.Start().ConfigureAwait(true); // 开始执行
// 2.准备任务
var jobBuilder = JobBuilder.Create<TestJob>(); // 创建任务构造者:JobBuilder
var job = jobBuilder.WithIdentity(@"jobId", @"groupName").Build(); // 创建任务
job.JobDataMap.Put(@"jobData", new object());
// 3.准备触发器
var triggerBuilder = TriggerBuilder.Create().StartNow() // 立即生效
.WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()); // 创建触发器构造者:TriggerBuilder, 设置每 5 秒运行一次,并一直运行
var trigger = triggerBuilder.WithIdentity("triggerId", "groupName").Build() as ISimpleTrigger; // 创建触发器
// 4.将任务与触发器添加到调度器中, 如果 trigger 用 ForJob 与 job 关联,则只需要传入 trigger
await scheduler.ScheduleJob(job, trigger).ConfigureAwait(false);
Console.WriteLine(@"测试完成,按任务键结束");
Console.ReadKey();
// 5. 关闭服务
await scheduler.Shutdown().ConfigureAwait(false);
}
// 测试任务
public class TestJob : IJob
{
// 任务名
public string JobName { get; set; }
// 运行任务
public async Task Execute(IJobExecutionContext context)
{
// 取出传入的数据
var map = context.MergedJobDataMap;
var jobName = map.GetString(@"jobName");
jobName = map.GetString(@"JobName");
await Task.Run(() => Console.WriteLine($@"{this.JobName} {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} 运行"))
.ConfigureAwait(false);
}
}
传入数据至 Job
// 传入数据
//// 使用 jobBuilder 传入 jobBuilder.UsingJobData(@"jobData", @"dataValue") // 该方式传入基本数据
//// 使用 job 传入 job.JobDataMap.Put(@"jobData", new object()); // 该方式可以传入 object 对象
//// 使用 triggerBuilder 传入 triggerBuilder.UsingJobData(@"triggerData", @"dataValue")
//// 使用 trgger 传入 trigger.JobDataMap.Put(@"triggerData", @"dataValue");
// 提取数据
public async Task Execute(IJobExecutionContext context)
{
// 1. 取出 job 中传入的数据
context.JobDetail.JobDataMap;
// 2. 取出传入的数据, 会自动合并 trigger 及 job 中的数据,
var map = context.MergedJobDataMap;
// 3. 设置属性,jobfactory 自动使用 jobdata 进行填充
public this.JobName {get;set;}
// 4. 提取数据
var jobName = map.GetString(@"jobName");
jobName = map.GetString(@"JobName");
}
数据实例调用方式
// 创建多个实例
// 定义一个 ijob , 然后 jobBuilder.Build() 出多个实例,每个实例传入不同的 jobData 以实现不同的业务
// 任务不允许并发调用
// [DisallowConcurrentExecution] class TestJob : IJob // 任务不允许并发调用
// job 间 传递 jobdata
// [PersistJobDataAfterExecution] class TestJob : IJob // 任务设置 JobDataMap 的数据将传递给下一个 job, 建议设置 DisallowConcurrentExecution, 这样就不会引发并发问题
// 自动重启任务
jobBuilder.RequestRecovery(true); // 调度程序重启时该作业会重新执行
// 自动删除任务
jobBuilder.StoreDurably(true); // false: 那么当没有任何活动的触发器与之关联时, 该作业将被调度程序自动删除
IJob.Execute 失败处理
建议抛出 JobExecutionException 以控制调度器
throw new JobExecutionException()
{
RefireImmediately = false, // 要不要重启此任务
UnscheduleFiringTrigger = true, // 停止引发 trigger
UnscheduleAllTriggers = true // 停止所有 trigger
};
触发器属性
启动停止时间
// 启动时间
triggerBuilder.StartNow() // 立即启动
triggerBuilder.StartAt() // 指定时间启动
triggerBuilder.StartAt(DateBuilder.FutureDate(5, IntervalUnit.Day)); // 下一个相应时间启动
triggerBuilder.StartAt(DateBuilder.EvenHourDate(null)); // 下一个指定时间的整点 (null 为当前时间 )
// 对时间计算的一个详细说明 https://www.cnblogs.com/yaopengfei/p/8520659.html
// 停止时间
triggerBuilder.EndAt() // 指定停止时间
// 优先级
triggerBuilder.WithPriority() // 优先级
排除时间
// 设置排除时间, 实现 ICalendar, 或是使用 HolidayCalendar 排除一整天 ,
var cal = new HolidayCalendar();
cal.AddExcludedDate(someDate); // 定义排除日期
scheduler.AddCalendar(@"cal", cal); // 设置调度器的排除时间
triggerBuilder.ModifiedByCalendar() // 或是设置触发器的排除时间
简单时间间隔
// 设置简单重复规则
triggerBuilder.WithSimpleSchedule(x => )
复杂时间间隔
// 使用7段设置 秒 分 时 日 月 星期 年(可选)
triggerBuilder.WithCronSchedule("0 0 0 0 0 0 0");
// 具体值:比如: 0 0 12 ? * WED // 每周三下午 12 点, 月使用 0-11,或是 JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 和 DEC, 星期使用 SUN, MON, TUE, WED, THU, FRI 和 SAT 或是 1-7 (1 为周日),
// 范围值: 比如 小时的 3-15 表示,表示 3 至 15 时,
// 枚举值:比如 小时的 3, 5, 10 表示, 3、5、10 时
// *: 所有可能值,比如:0 0 12 ? * WED // * 在月的位置,表示所有月
// ?: 用于日、星期,表示该字段由另一个字段设置,比如 0 0 12 ? * WED // ? 在日的位置,表示由其它决定,该值都可以。注意与*不同
// /: 表示 启始/步进值, 比如分钟中 3/20, 指3分开始,每 20 分钟执行一次
// L: last 表示最后一天,比如在日中表示该月的最后一天
// W: weekday 表示工作日,比如 日中的 15W 表示 15日,如果15日不是工作日,则为最近的那天的工作日
// #: 在星期中表示该月中的第 n 个星期,比如 6#3 (FRI#3) 该月中的第3个周五
// 例子
// "0 0/5 * * * ?" // 每5分钟触发一次
// "10 0/5 * * * ?" // 每5分钟10秒触发一次
// "0 30 10-13 ? * WED,FRI" // 每周三和周五的 10:30, 11:30, 12:30 和 13:30 触发.
// "0 0/30 8-9 5,20 * ?" // 在每月的5、20日上午8点到10点之间每半小时触发一次
触发详细说明
https://www.cnblogs.com/yaopengfei/p/8542771.html
一. WithSimpleSchedule(ISimpleTrigger)
1. 用途:时、分、秒上的轮询(和timer类似),实际开发中,该场景占绝大多数.
2. 轮询的种类:永远轮询和限定次数轮询.
3. 参数中的几个函数:
A.执行间隔:
①.WithInterval(TimeSpan timeSpan):通用的间隔执行方法
②.WithIntervalInHours(int hours):以小时为间隔单位进行执行
③.WithIntervalInMinutes(int minutes):以分钟为间隔单位进行执行
④.WithIntervalInSeconds(int seconds):以秒为间隔单位进行执行
B.执行时间:
①.WithRepeatCount(int repeatCount):执行多少次以后结束
②.RepeatForever():永远执行
③.repeatMinutelyForever():一分钟执行一次(永远执行)
repeatMinutelyForever(int minutes):每隔几分钟执行一次(永远执行)
repeatMinutelyForTotalCount(int count, int minutes):每隔几分钟执行一次(执行次数为count)
类似的还有秒、小时。
代码分享:
复制代码
1 public static void SimpleTriggrShow()
2 {
3 //1. 创建Schedule
4 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
5 //2. 创建Job
6 var job1 = JobBuilder.Create<HelloJob>().Build();
7 //3. 创建Trigger
8 //1s执行一次,永远执行
9 var trigger = TriggerBuilder.Create()
10 .WithSimpleSchedule(x => x.WithIntervalInSeconds(1).RepeatForever())
11 .Build();
12 //2s执行一次,执行10次
13 //var trigger = TriggerBuilder.Create()
14 // .WithSimpleSchedule(x => x.WithIntervalInSeconds(2).WithRepeatCount(10))
15 // .Build();
16 //注意这种用法:WithScheduler,表示1s执行一次,执行了5次
17 //var trigger = TriggerBuilder.Create()
18 // .WithSchedule(SimpleScheduleBuilder.RepeatSecondlyForTotalCount(5, 1))
19 // .Build();
20 //4. 开始调度
21 scheduler.ScheduleJob(job1, trigger);
22 scheduler.Start();
23 }
复制代码
二. WithCalendarIntervalSchedule (ICalendarTrigger)
1.用途:与日历相关
2.参数中的几个函数:
①.WithInterval(TimeSpan timeSpan):通用的间隔执行方法
②.WithIntervalInHours(int hours):以小时为间隔单位进行执行
③.WithIntervalInMinutes(int minutes):以分钟为间隔单位进行执行
④.WithIntervalInSeconds(int seconds):以秒为间隔单位进行执行
⑤.WithIntervalInDays(int days):以天为间隔单位进行执行
⑥.WithIntervalInMonths(int months):以月为间隔单位进行执行
代码分享:
复制代码
1 public static void CalendarIntervalTriggerShow()
2 {
3 //1. 创建Schedule
4 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
5 scheduler.Start();
6 //2. 创建Job
7 var job1 = JobBuilder.Create<HelloJob>().Build();
8 //3. 创建Trigger
9 //3s执行一次,60s后结束
10 var trigger = TriggerBuilder.Create()
11 .WithCalendarIntervalSchedule(x => x.WithIntervalInSeconds(3))
12 .EndAt(DateTimeOffset.Now.AddSeconds(60)) //60s后结束
13 .Build();
14 //4. 开始调度
15 scheduler.ScheduleJob(job1, trigger);
16 }
复制代码
三. WithDailyTimeIntervalSchedule (IDailyTimeTrigger)
1. 用途:解决时间点的增、减、排除。
2. 核心函数:
a. OnEveryDay:每天
b. OnMondayThroughFriday:周一至周五,即工作日
c. OnSaturdayAndSunday:周六至周天,即休息日
d. OnDaysOfTheWeek:用数组的形式单独来指定一周中的哪几天
e. StartingDailyAt:表示开始于几点 (区别于前面的StartAt)
f. EndingDailyAt:表示结束于几点 (区别于前面的EndAt)
代码分享:
复制代码
1 public static void DailyTimeIntervalTriggerShow()
2 {
3 //1. 创建Schedule
4 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
5 //2. 创建Job
6 var job1 = JobBuilder.Create<HelloJob>().Build();
7 //3. 创建Trigger
8 //每天8-20点,每半个小时执行一次(即8:00、8:30 。。。。 19:30、20:30)
9 var trigger1 = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
10 x => x.OnEveryDay()
11 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 00))
12 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(20, 00))
13 .WithIntervalInMinutes(30))
14 .Build();
15
16 //每个工作日的凌晨2点执行1次 (这里的设计是2点开始,2:01结束,每个一小时执行一次,说白了总共执行了一次)
17 //或者直接WithIntervalInHours替换成WithRepeatCount
18 var trigger2 = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
19 x => x.OnMondayThroughFriday()
20 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(2, 00))
21 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(2, 01))
22 .WithIntervalInHours(1))
23 .Build();
24
25 //每个周的周一和周四的2点执行1次 (这里的设计是2点开始,2:01结束,每个一小时执行一次,说白了总共执行了一次)
26 //或者直接WithIntervalInHours替换成WithRepeatCount
27 var trigger3 = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
28 x => x.OnDaysOfTheWeek(new DayOfWeek[2] {
29 DayOfWeek.Monday, DayOfWeek.Thursday })
30 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(2, 00))
31 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(2, 01))
32 .WithIntervalInHours(1))
33 .Build();
34 //4. 开始调度
35 scheduler.ScheduleJob(job1, trigger1);
36 scheduler.Start();
37 }
复制代码
四. WithCronSchedule (ICronTrigger)
1. 用途:使用cron表达式代替硬编码,可以替代以上三种Trigger (详见:www.cnblogs.com/knowledgesea/p/4705796.html)
2. 规则:
a 整体规则排列如下,且日和周必须有一个位数是 ?
* * * * * *
秒 分 时 日 月 周
b ?: 代表示模糊的意思,必须存在,且只能在日或周中的一个存在
c *: 最小单位轮询,在分钟的字段域里,表示每分钟;在小时的字段域里,表示每小时
d /: 表示递增: 如0/5在秒的字段域里,表示第0、5、15、20.... 秒 可以省略0,即 /5
e -: 表示范围, 如1-10在秒字段域里,表示1s、2s、3s到10s都执行
f ,: 表示并且, 如1,10,20在秒字段域里,表示1s,10s,20s都执行
g #: 只能存在周这一个域,表示第几周的星期几,如果超出范围,则忽略不记,如2#4,表示第四周的星期二
h L: 表示last的意思: 天: 10L 表示本月的倒数第十天执行, 5L 表示本月的最后一个周四执行(暂不研究)
3. 补充一下秒、分、时、日、月、周的字段域范围
秒: 0-59
分: 0-59
时: 0-23
日: 1-31
月: 1-12 或 JAN-DEC
周: 1-7 或 SUN-SAT
年:选填,可以留空, 1970-2099
4. 补充几个事例帮助理解:
实例1:0**1*? note:每月1号凌晨都会被执行。
实例2:0**?** note:每分钟的00秒被执行。
实例3:0 10 18 ? 3 WEB note:每年3月的每个星期三,下午6点10分都会被触发
实例4:0 10 18 15 3 ? note:每年三月的第15天,下午6点10分都会被触发
实例5:0 10 18 1-5 * ? note:每月的1号到5号(包含每月1号和5号,每月共计5天都会被触发),下午6点10分都会被触发
实例6:0 10-15 * ? * * note:每小时的第10分钟到第15分钟(包含每小时的第10分钟和第15分钟,每小时共计5分钟都会被触发),都会被触发
实例7:10,20 * * ? * * note:每分钟的第10秒与第20秒都会被触发
实例8:0 10,20 * 1,2 * ? note:每月的第1天与第2天的,每小时的第10分钟与第20分钟被触发。
实例9:5/20 * * ? * * note:每分钟的第5秒,第25秒,第45秒 都会被执行。
实例10:0 * 2/2 ? * * note:每天的第2小时,第4小时,第6小时,第8小时 ... 第22小时的00分00秒都会被触发。
实例11:* * * ? * 3#4 note:每月的第4个星期的周2,凌晨触发。
实例12:* * * ? * 6#2 note:每月的第2个星期的周5,凌晨触发
代码分享:
复制代码
1 public static void CronTriggerShow()
2 {
3 //1. 创建Schedule
4 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
5 scheduler.Start();
6
7 //2. 创建Job
8 var job1 = JobBuilder.Create<HelloJob>().Build();
9
10 //3. 创建Trigger
11
12 //每天8-20点,每半个小时执行一次(即8:00、8:30 。。。。 19:30、20:30)
13 var trigger1 = TriggerBuilder.Create().WithCronSchedule("0 0/30 8-20 * * ?")
14 .Build();
15 //每个工作日的凌晨2点执行1次
16 var trigger2 = TriggerBuilder.Create().WithCronSchedule("0 0 2 ? * Mon-Fri")
17 .Build();
18
19 //每个周的周一和周四的2点执行1次
20 var trigger3 = TriggerBuilder.Create().WithCronSchedule("0 0 2 ? * Mon,Wes")
21 .Build();
22
23
24 //4. 开始调度
25 scheduler.ScheduleJob(job1, trigger2);
26 }
排除时间详细说明
https://www.cnblogs.com/yaopengfei/p/8545777.html
背景介绍及其使用
该章节主要补充介绍,在前一章四类触发器的基础上配合六大Canlander来动态删减某些时间,来满足更多的应用场景。
\1. DailyCalendar:动态排除某天的某些字段.
(需求:每天8-23点执行,每隔1s执行一次,但是21-22点这个区间不执行)
\2. WeeklyCalendar:适合在星期几的维度上做“减法操作”
(需求:每天8-23点执行,每隔1s执行一次,但是周五这一天不执行)
\3. HolidayCalendar:适合当年的某一天不能执行
(需求:每天8-23点执行,每隔1s执行一次,但是今年的6月16号这一天不执行)
\4. MonthlyCalendar:适合某个月中的某一天不能执行
(需求:每天8-23点执行,每隔1s执行一次,但是每月的27号不执行)
\5. AnnualCalendar:适合每年指定的某一天不能执行(有问题)
(需求:每天8-23点执行,每隔1s执行一次,但是每年的6月16号这一天不执行)
\6. CronCalendar:字符串表达式来排除某一天,某一个月份,某一年都可以
(需求:每天8-23点执行,每隔1s执行一次,但是2月27号这天不执行)
代码分享:
1 public static void CalanderShow()
2 {
3 //1. 每天8-23点执行,每隔1s执行一次,但是21-22点这个区间不执行
4 {
5 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
6 scheduler.Start();
7 //单独记录一个区间段 21-22点
8 DailyCalendar dailyCalendar = new DailyCalendar(DateBuilder.DateOf(21, 0, 0).DateTime,
9 DateBuilder.DateOf(22, 0, 0).DateTime);
10 scheduler.AddCalendar("mycalendar", dailyCalendar, true, true);
11
12 var job = JobBuilder.Create<HelloJob>().Build();
13 var trigger = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
14 x => x.OnEveryDay()
15 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 00))
16 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(23, 00))
17 .WithIntervalInSeconds(1)
18 )
19 .ModifiedByCalendar("mycalendar")
20 .Build();
21 scheduler.ScheduleJob(job, trigger);
22 }
23
24 //2. 每天8-23点执行,每隔1s执行一次,但是周五这一天不执行
25 {
26 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
27 scheduler.Start();
28 //设定周五不能执行
29 WeeklyCalendar calendar = new WeeklyCalendar();
30 calendar.SetDayExcluded(DayOfWeek.Friday, true);
31 scheduler.AddCalendar("mycalendar", calendar, true, true);
32
33 var job = JobBuilder.Create<HelloJob>().Build();
34 var trigger = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
35 x => x.OnEveryDay()
36 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 00))
37 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(23, 00))
38 .WithIntervalInSeconds(1)
39 )
40 .ModifiedByCalendar("mycalendar")
41 .Build();
42 scheduler.ScheduleJob(job, trigger);
43 }
44
45 //3. 每天8-23点执行,每隔1s执行一次,但是当年6月16号这一天不执行
46 {
47 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
48 scheduler.Start();
49 HolidayCalendar calendar = new HolidayCalendar();
50 calendar.AddExcludedDate(DateTime.Parse("06-16")); //把当年6月16日排除在外
51
52 scheduler.AddCalendar("mycalendar", calendar, true, true);
53 var job = JobBuilder.Create<HelloJob>().Build();
54 var trigger = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
55 x => x.OnEveryDay()
56 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 00))
57 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(23, 00))
58 .WithIntervalInSeconds(1)
59 )
60 .ModifiedByCalendar("mycalendar")
61 .Build();
62 scheduler.ScheduleJob(job, trigger);
63
64 }
65
66 //4. 每天8-23点执行,每隔1s执行一次,但是每月的27号不执行
67 {
68 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
69 scheduler.Start();
70
71 //指定月份中的某一天不能执行
72 MonthlyCalendar calendar = new MonthlyCalendar();
73 calendar.SetDayExcluded(27, true); //将27号这天排除在外
74 scheduler.AddCalendar("mycalendar", calendar, true, true);
75
76 var job = JobBuilder.Create<HelloJob>().Build();
77 var trigger = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
78 x => x.OnEveryDay()
79 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 00))
80 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(23, 00))
81 .WithIntervalInSeconds(1)
82 )
83 .ModifiedByCalendar("mycalendar")
84 .Build();
85
86 scheduler.ScheduleJob(job, trigger);
87 }
88
89 //5. 每天8-23点执行,每隔1s执行一次,但是每年的6月16号这一天不执行
90 {
91 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
92 scheduler.Start();
93
94 AnnualCalendar calendar = new AnnualCalendar();
95 calendar.SetDayExcluded(DateTime.Parse("06-16"), true); //把每年的6月16日排除在外
96 scheduler.AddCalendar("mycalendar", calendar, true, true);
97
98 var job = JobBuilder.Create<HelloJob>().Build();
99 var trigger = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
100 x => x.OnEveryDay()
101 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 00))
102 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(23, 00))
103 .WithIntervalInSeconds(1)
104 )
105 .ModifiedByCalendar("mycalendar")
106 .Build();
107 scheduler.ScheduleJob(job, trigger);
108 }
109
110 //6.每天8-23点执行,每隔1s执行一次,但是2月27号这天不执行
111 {
112 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
113 scheduler.Start();
114
115 CronCalendar calendar = new CronCalendar("* * * 27 2 ?");
116 scheduler.AddCalendar("mycalendar", calendar, true, true);
117
118 var job = JobBuilder.Create<HelloJob>().Build();
119 var trigger = TriggerBuilder.Create().WithDailyTimeIntervalSchedule(
120 x => x.OnEveryDay()
121 .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(8, 00))
122 .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(23, 00))
123 .WithIntervalInSeconds(1)
124 )
125 .ModifiedByCalendar("mycalendar")
126 .Build();
127 scheduler.ScheduleJob(job, trigger);
128 }
129
130 }
哑火
错过触发时间(重启、线程池不足等等)
https://www.cnblogs.com/yaopengfei/p/8549508.html
1. 什么是哑火
由于某些原因导致触发器(trigger)在该触发的时候没有得到触发,后续对应的解决策略即为哑火策略。(个人理解)
2. 哑火触发的条件
①:所有的工作线程都在忙碌,导致某些trigger得不到触发.(如:simplethreadpool 默认是10个工作线程,但我有15个trigger同时触发, 恰巧这10个trigger关联的job耗时都很长,剩下的5个trigger超过了等待时间仍然没有得到触发)
②:调度器(sheduler)中途挂了,某个时间又恢复了
③:设置的trigger的开始时间早于当前时间
如果没有触发哑火的条件,则不会触发哑火策略,之前的全部都是按照忽略来计算的,后续按照正常规律来进行。如果不设置开始时间,即为当前时间开始,不触发哑火策略。
下面介绍两类Trigger对应的哑火策略:SimpleTrigger和CronTrigger (前提:案例设置时间早于当前时间的,即都是有错过的)
二. SimpleTrigger哑火策略详解
这里要分三种情况来讨论,执行指定次数的情况WithRepeatCount(n),n=1、n>1、n=forever三种情况,重点理解n>1的情况,另外两种都是他的特殊情况而已。
(一):执行指定次数的情况(WithRepeatCount(n) n>1) 只要记住这种情况,下面的B和C都能分析出来
(1).默认 :立即执行,修改当前调度时间,总数保持不变 (等价于WithMisfireHandlingInstructionNowWithExistingCount)
详见下面解析
(2).WithMisfireHandlingInstructionIgnoreMisfires:错过的立即追赶,然后正常调度。
PS:设置的时间早于当前时间,执行的时候,会将当前时间之前错过的次数一次性执行完,然后正常按照设置的开始时间及规律进行执行。 如果设置的RepeatCount(n)中n的次数小于错过的次数,只能执行n次,执行完后,将不会在执行了,因为执行次数已经用完了。
案例:设置的开始时间为8:00,每隔半小时执行一次,执行总次数为5次,当前时间为9:05,那么开始时候会先执行3次,将错过的一次性执行了,然后按照正常调度执行,下一次执行的时间为9:30,还能执行两次。
(3).WithMisfireHandlingInstructionNextWithExistingCount:错过的不管了,按计划等待下次调度,总数不变,结束时间推迟。
PS:错过的次数不处理,仍按照设置的规律来执行,执行次数不变,要执行完的RepeatCount(n)中的n.
案例:设置的开始时间为8:00,每隔半小时执行一次,执行总次数为5次,当前时间为9:05,那么开始时候并没有调度执行,第一次执行调度的时间为9:30,然后按照正常调度执行,总共执行5次,最后一次时间为11:30。
(4).WithMisfireHandlingInstructionNextWithRemainingCount:错过的不管了,按计划等待下次调度,但总数要减去misfire错过的次数
PS:错过的次数不处理,仍按照设置的规律来执行,执行总数要 减去 错过的次数!
案例:设置的开始时间为8:00,每隔半小时执行一次,执行总次数为5次,当前时间为9:05,那么开始时候并没有调度执行,第一次执行调度的时间为9:30,然后按照正常调度执行,总共执行 5-3=2 次,最后一次时间为10:00。
(5).WithMisfireHandlingInstructionNowWithExistingCount: 立即执行,修改当前调度时间,总数保持不变
PS:立即执行,修改当前调度时间的含义为,即使我设置的开始时间早于当前时间,但该哑火策略会立马执行该触发器,即运行后,马上执行了一次,后续的时间间隔均是是以当前执行时间为基础来进行的,言外之意,之前设置的开始时间没用了,执行总数不变。
案例:设置的开始时间为8:00,每隔半小时执行一次,执行总次数为5次,当前时间为9:05,那么开始时候立即执行,第一次执行调度的时间为9:05,然后以9:05为基础,按照正常调度规律执行,总共执行5次,第二次时间为9:35,最后一次执行时间为11:05。
(6).WithMisfireHandlingInstructionNowWithRemainingCount: 立即执行,修改当前调度时间,总数要减去misfire错过的次数
PS:立即执行,修改当前调度时间的含义为,即使我设置的开始时间早于当前时间,但该哑火策略会立马执行该触发器,即运行后,马上执行了一次,后续的时间间隔均是是以当前执行时间为基础来进行的,言外之意,之前设置的开始时间没用了。执行总数要 减去 错过的次数!
案例:设置的开始时间为8:00,每隔半小时执行一次,执行总次数为5次,当前时间为9:05,那么开始时候立即执行,第一次执行调度的时间为9:05,然后以9:05为基础,按照正常调度规律执行,总共执行 5-3=2 次,第二次(即最后一次)时间为9:35。
B:只执行一次的情况 (WithRepeatCount(n) n=1) 对A情况的一个特殊分析
(1). 默认:(等价于WithMisfireHandlingInstructionFireNow)
(2). WithMisfireHandlingInstructionFireNow:立即执行
PS:设置的时间早于当前时间,执行的时候,立即把这一次执行完,后续将不再执行;但设置的时间晚于当前时间,则按照正常规律进行执行了
(3).WithMisfireHandlingInstructionNextWithRemainingCount 和 WithMisfireHandlingInstructionNowWithRemainingCount :不执行了
PS:设置的时间早于当前时间,执行的时候按照原规律执行,但是次数要减去错过的次数,这里总共就执行一次,所以就不执行了
C:永久执行的情况(RepeatForever) 对A情况的一个特殊分析
(1). 默认:等价与下面的(2),按计划的正常调度执行,执行次数永久执行
(2). WithMisfireHandlingInstructionNextWithExistingCount 和 WithMisfireHandlingInstructionNextWithRemainingCount:按计划的正常调度执行,执行次数永久执行
(3). WithMisfireHandlingInstructionNowWithExistingCount 和 WithMisfireHandlingInstructionNowWithRemainingCount: 立即执行,执行的开始时间改为当前时间,执行次数永久执行
代码分享:自行替换即可
复制代码
1 public static void misfireShow()
2 {
3 //1.创建Schedule
4 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
5 //2.创建job (具体的job需要单独在一个文件中执行)
6 var job = JobBuilder.Create<HelloJob4>().Build();
7 //3.配置trigger
8 //下面的四个触发器对应了上面四种情况的测试,时间需要根据实际情况进行配置测试来设置
9 var trigger1 = TriggerBuilder.Create().StartAt(DateBuilder.DateOf(20, 0, 0))
10 .WithSimpleSchedule(x => x.WithIntervalInMinutes(30)
11 .WithRepeatCount(8)
12 .WithMisfireHandlingInstructionNowWithExistingCount()
13 ).Build();
14 var trigger2 = TriggerBuilder.Create().StartAt(DateBuilder.DateOf(18, 0, 0))
15 .WithSimpleSchedule(x => x.WithIntervalInSeconds(1)
16 .WithRepeatCount(1)
17 .WithMisfireHandlingInstructionFireNow()
18 ).Build();
19 var trigger3 = TriggerBuilder.Create()
20 .StartAt(DateBuilder.DateOf(15, 0, 0, 5, 3, 2018))
21 .WithSimpleSchedule(x => x.WithIntervalInMinutes(1)
22 .WithMisfireHandlingInstructionNowWithRemainingCount()
23 .RepeatForever()).Build();
24 var trigger4 = TriggerBuilder.Create()
25 .StartAt(DateBuilder.DateOf(14, 0, 0))
26 .WithCronSchedule("0 0-59 9-23 ? * MON-FRI",x=>x.WithMisfireHandlingInstructionIgnoreMisfires()).Build();
27
28 //4.开始调度
29 scheduler.ScheduleJob(job, trigger1);
30 scheduler.Start();
31 }
复制代码
三. CronTrigger哑火策略详解
(1). 默认:错过的合并,于当前时间执行一次,不修改调度时间,按计划等待下一次调度(等价于下面的 WithMisfireHandlingInstructionFireAndProceed)
(2). WithMisfireHandlingInstructionIgnoreMisfires:错过的立即追赶,然后正常调度
ps:错过多少次,初次执行的时候追赶多少次,追赶的次数的时间是按原规律执行的时间,然后按照原规律进行正常后续调度
(3). WithMisfireHandlingInstructionFireAndProceed:错过的合并,于当前时间执行一次,不修改调度时间,按计划等待下一次调度
PS:无论错过多少次,均在初次运行的时候,即当前时间执行一次,后续的执行仍按照原规律进行执行。
(4). WithMisfireHandlingInstructionDoNothing:错过的不管了,后续按照正常调度进行
PS:无论错过多少次,均忽略,后续的执行仍按照原规律进行执行。
触发器监听器及JOB监听器
// 实现 ITriggerListener 在触发器触发、完成时接收事件
// 实现 IJobListener 在 JOB 执行前及执行完成后接收事件
// 监听指定组中指定任务
scheduler.ListenerManager.AddJobListener(myJobListener, KeyMatcher<JobKey>.KeyEquals(new JobKey("myJobName", "myJobGroup")));
// 监听指定组任务
scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.GroupEquals("myJobGroup"));
// 监听所有任务
scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.AnyGroup());
默认线程池
默认线程池为 10 线程
// 1 代码设置,
var pairs = new System.Collections.Specialized.NameValueCollection() { };
pairs.Add("quartz.threadPool.ThreadCount", "20"); //设置线程池个数为20
// 设置一个调度器
var factory = new StdSchedulerFactory(pairs); //将前面的配置加到Scheduler工厂中
// 2 App.config, 全局
<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</configSections>
<quartz>
<!--设置Sheduler的线程池个数为22-->
<add key="quartz.threadPool.threadCount" value="22"/>
</quartz>
// 3 工作目录下创建 quartz.config
quartz.threadPool.threadCount=15
// 4 启动前设置环境变量
//将线程池的个数设置为26
Environment.SetEnvironmentVariable("quartz.threadPool.threadCount", "26");
// 同时设置是优先级 quartz.config < app.config < 环境变量 < namevaluecollection
存储
默认使用内存存储
// 默认使用内存存储
quartz.jobStore.type = Quartz.Simpl.RAMJobStore
其它数据库
// 目前内部只实现了 JobStoreTX 引擎
quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
// 1. 指定委托,通用为 StdAdoDelegate,可能会有性能问题 可以在 Quartz.Impl.AdoJobStore 中查看更多委托, 比如 Quartz.Impl.AdoJobStore.MySQLDelegate
quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz
// 2. 数据表前缀
quartz.jobStore.tablePrefix = QRTZ_
// 3. 数据源名称
quartz.jobStore.dataSource = myDS
// 4. 数据库连接
quartz.dataSource.myDS.connectionString = Server=localhost;Database=quartz;Uid=quartznet;Pwd=quartznet
quartz.dataSource.myDS.provider = MySql
调度工厂
https://www.cnblogs.com/taadis/p/quartz-3-x-tutorial-configuration-resource-usage-and-scheduler-factory.html
Quartz 是以模块化的方式构建的, 因此要使其运行, 需要将几个组建 "拼凑" 在一起. 幸运的是, 有一些小伙伴可以帮我做到这一点.
Quartz 正常工作前需要配置的主要组件有:
ThreadPool - 线程池
JobStore - 作业查询
DataSources (如有必要) - 数据源
The Scheduler itself - 调度程序本身
自从引入基于 Task 的作业以来线程池发生了很大的变化. 文档待补充
在本教程的 课程 9 中讨论了 JobStore 和 DataSources. 值得注意的是, 实际上所有的 JobStores 都实现了 IJobStore 接口 - 如果捆绑的 JobStores 没有您需要的, 您可以自己实现.
最后, 您需要创建您的调度程度实例. 调度程序本身需要被赋予 JobStore 和 ThreadPool 的名称和实例.
StdSchedulerFactory
StdSchedulerFactory 是 ISchedulerFactory 接口的一个实现. 她使用了一组属性 (NameValueCollection) 来创建和初始化 Quartz 调度程序. 这些属性通常存储在文件中并从文件中加载, 但是也可以由您的程序创建并直接传递到工厂. 只需在调用工厂中的 getScheduler() 方法即可生成调度程度, 初始化其(以及其 ThreadPool, JobStore 和 DataSources), 并返回其公共接口的句柄.
这里有一些配置示例 (包括属性描述) 在 Quartz 发布的 "docs/config" 目录下. 您可以在 Quartz 文档的 "Reference" 部分的 "Configuration" 下找到完整的文档.
DirectSchedulerFactory
DirectSchedulerFactory 是另一个 SchedulerFactory 实现. 对于那些希望以更具编程性的方式创建调度程序的的人来说是很有用的. 但通常不鼓励这样使用原因如下: (1) 这要求用户对他们正在做的事有更深入的理解, 以及 (2) 这不允许声明式配置 - 也就是说, 您最终需要硬编码调度程序的所有设置.
Logging
Quartz.NET 使用 LibLob 库来满足其所有的日志需要. Quartz 不会产生许多日志信息 - 通常只有一些初始化期间的信息, 以及在作业执行期间有关严重问题的信息. 为了 "调优" 日志设置 (例如日志量, 以及输出位置), 您需要实际配置您选择的日志框架因为 LibLog 主要将工作委托给更成熟的日志框架如 log4net, serilog 等.
远程控制
https://www.cnblogs.com/yaopengfei/p/8573628.html
注意
使用 iis 布置时,iis 有定时回收机制。需要关闭该机制
https://www.cnblogs.com/yaopengfei/p/8613198.html
https://www.cnblogs.com/taadis/p/quartz-3-x-tutorial-cron-triggers.html
https://www.jianshu.com/p/3d5b487c6860
https://blog.csdn.net/lee_xuwei/article/details/95311610
https://github.com/mcxiaoke/mqtt
