Quartz.NET 架构与源代码分析系列 part 3 :Trigger 触发器

      在上一篇《Quartz.NET 架构与源代码分析系列 part 2 :Job 作业》我们分析了Quartz.NET 的 IJob 继承结构以及Quartz.NET 附带的几个 Job类。现在,我们要介绍 Job 执行的条件,也就是 Trigger 触发器。

Trigger 抽象类

      Quartz.NET 有一个 Trigger 抽象类,它有一些重要的方法和参数,已经标在图1 上。

image  image

图1 Trigger 抽象类图

      从图2 可以看出,Quartz.NET 实现了3个具体的触发器类,SimpleTrigger 简单地在某一时间重复执行多少次,NthIncludedDayTrigger 在每一年、月、周的第几天(Nth)执行作业,CronTrigger 使用 Unix 平台下的'cron-like’表达式来实现非常灵活的触发时间。

image图2 Trigger 继承结构

SimpleTrigger

       我们来看一下SimpleTrigger 的构造函数:

/// <param name="name">触发器名</param>
/// <param name="group">触发器组名</param>
/// <param name="jobName">绑定的任务名</param>
/// <param name="jobGroup">绑定的任务组名</param>
/// <param name="startTimeUtc">开始执行时间A <see cref="DateTime" /> set to the time for the <see cref="Trigger" />
/// to fire.</param>
/// <param name="endTimeUtc">结束执行时间A <see cref="DateTime" /> set to the time for the <see cref="Trigger" />
/// to quit repeat firing.</param>
/// <param name="repeatCount">重复触发次数The number of times for the <see cref="Trigger" /> to repeat
/// firing, use RepeatIndefinitely for unlimited times.</param>
/// <param name="repeatInterval">触发间隔The time span to pause between the repeat firing.</param>
public SimpleTrigger(string name, string group, string jobName, string jobGroup, DateTime startTimeUtc,
         NullableDateTime endTimeUtc,
         int repeatCount, TimeSpan repeatInterval)
    : base(name, group, jobName, jobGroup)
{
    StartTimeUtc = startTimeUtc;
    EndTimeUtc = endTimeUtc;
    RepeatCount = repeatCount;
    RepeatInterval = repeatInterval;
}

      它有一个起始时间和结束时间,起始时间触发器触发,过了结束时间触发器停止触发。时间间隔 Interval,触发次数 RepeatCount。

sinpleTirgger

图3 SimpleTrigger 触发器工作示例图

CronTrigger

       CronTrigger 使用 UNIX 下的“Cron-like” 表达式,实际上用起来感觉它很像正则表达式,可以匹配任意时间,这是体现它灵活性的地方。它的规则如下:

Cron 表达式包括以下 7 个字段(1 个可选)

秒  分 小时 月内日期 月 周内日期 年(可选)

表达式的每个数值域都是一个有最大值和最小值的集合,如:秒域和分钟域的集合是0-59,日期域是1-31,月份域是1-12。注意:秒、分、小时字段是从小到大排序的,这是西方人的习惯,所以在使用的时候要小心,不要颠倒过来。

允许值及对应表见表1。

表1. Cron 表达式允许值及对应表

字段 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
月内日期 1-31 , - * ? / L W C
1-12 或者 JAN-DEC , - * /
周内日期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空, 1970-2099 , - * /
特殊字符意义对应表见表2。

表2. Cron 表达式特殊字符意义对应表

特殊字符

意义

*

匹配所有的值。如:*在分钟的字段域里表示 每分钟

?

只在日期域和星期域中使用。它被用来指定“非明确的值”

-

指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”

,

指定几个可选值。如:“MON,WED,FRI”在星期域里表示“星期一、星期三、星期五”

/

指定增量。如:“0/15”在秒域意思是没分钟的0,15,30和45秒。“5/15”在分钟域表示没小时的5,20,35和50。符号“*”在“/”前面(如:*/10)等价于0在“/”前面(如:0/10)

L

表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如‘6L’就表示一个月的最后一个星期五

W

只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15好是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的

LW

L和W可以在日期域中联合使用,LW表示这个月最后一周的工作日

#

只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三

C

允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)

示例:

"0 0 0 1 1 ?”               每年元旦1月1日 0 点触发

"0 15 10 * * ? *"         每天上午10:15触发 
"0 15 10 * * ? 2005"   2005年的每天上午10:15触发

"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 ? * 6#3"        每月的第三个星期五上午10:15触发

 

应用示例

      下面是用SimpleTrigger 和 CronTrigger 实现的触发配置面板:

image image

image

 

代码下载:QuartzExampleWin32_Source.rar

下一篇,我们将要介绍:Schedule 调度器

参考文章

1.The Official Quartz.NET Tutorial

2.Quartz的cron表达式

 

目录:   Quartz.NET 架构与源代码分析系列

上一篇:Quartz.NET 架构与源代码分析系列 part 2 :Job 作业

下一篇:Quartz.NET 架构与源代码分析系列 part 4  :Schedule 调度器

posted @ 2009-03-15 13:08 Doho 阅读(3091) 评论(19) 编辑 收藏

 回复 引用 查看   
#1楼2009-03-15 15:09 | leoxu      
刚好正想用这个。
 回复 引用 查看   
#2楼[楼主]2009-03-15 15:23 | Doho      
--引用--------------------------------------------------
leoxu: 刚好正想用这个。
--------------------------------------------------------
Quartz.NET 的确是一个很好的调度框架 :)

 回复 引用   
#3楼2009-03-15 17:24 | xyly[未注册用户]
感谢分享!学习中。。。
 回复 引用   
#4楼2009-03-15 17:50 | Sunnny[未注册用户]
请问,我的业务中需要定义N个各种计划,并且这些计划是保存在数据库中.方便实现吗?每条记录就是一个执行计划。
举例说明:我要针对部分客户每周的星期一晚上9点发送报表.每个月的1号晚上7点发送给部分客户报表.说白了就是一条记录就是一个计划,这些计划我可以随意制定.

 回复 引用 查看   
#5楼[楼主]2009-03-16 11:52 | Doho      
@Sunnny
可以的,“一条记录”、“发送报表”在 Quartz.NET 框架下就是一个“作业”,“每周的星期一晚上9点发送报表”对应 Quartz.NET 的就是“触发器”,这些都是 Quartz.NET 可以实现的基本功能。
希望我的回答对你有帮助 :)

 回复 引用   
#6楼2009-03-17 12:43 | Sunnny[未注册用户]
谢谢楼主的回答!
我目前的业务是要求在业务端可以灵活的定义各种作业。
比如发送短信:我目前的实现是在业务端把各种类型的作业,按照我设置的业务规则保存下来。然后通过windows服务定时循环扫描,把符合规则的作业解析成我要发送的短信。目前已经很方便的实现一次性,多次,每日,每月,特殊日期,节假日,工作日,生日等等各种业务。
但是在性能方面我还是避免不了无谓的扫描记录。因为是通过windows服务每隔若干秒扫描一次,这样我一天如果没有新的作业,那么就会浪费性能。
还有有些作业要求实时性很强,比如我们OA的消息提醒功能,到了9点就提醒,不差一秒。而我的windows服务是定时扫描的。无法做到这点。请问能实现我无谓的扫描和作业的实时性吗?

 回复 引用   
#7楼2009-03-17 12:49 | Sunnny[未注册用户]
对了 我还想知道我数据库的“一条记录”,如何映射成框架下的一个”作业“。因为时间忙,没有时间研究代码。如果能实现这些功能,就太好了!严重值得去研究。
 回复 引用 查看   
#8楼[楼主]2009-03-17 16:06 | Doho      
@Sunnny
你好!Quartz.NET 不是基于扫描的,它是基于下一次触发时间,跟 Timer 控件是一个原理的。所以不存在扫描的性能问题。实时性也不用说了,类似Timer 控件。
要映射成“作业”实际上就是实例化一个 JobDetail 类,并把它跟触发器绑定,加入到调度器就可以了。JobDetail 类实例化非常简单,在我的
《Quartz.NET 架构与源代码分析系列 part 2 :Job 作业》有介绍。
当然,如果你想把 Quartz.NET 框架集成到你的项目中来,阅读一下代码那是必要的,大概知道它是什么原理就可以了。
希望我的回答对你有帮助! :)

 回复 引用   
#9楼2009-03-19 17:34 | 牛牛2008[未注册用户]
lz,能否实现web的管理页面啊,你这个貌似是winform的!
 回复 引用 查看   
#10楼[楼主]2009-03-21 11:06 | Doho      
@牛牛2008
可以的,就像 Lucene.NET 类库一样,Quartz.NET 可以用在 winform、Web 应用等。

 回复 引用   
#11楼2009-04-26 15:20 | Kuoching Ju
谢谢分享!
 回复 引用   
#12楼2009-05-04 14:27 | ZzTiger1[未注册用户]
请问要怎么修改触发器.
比如原先都是每周一上午10点执行一次,现在要改成每周五上午10点执行,要怎么修改呢?是不是要把原先的删除掉,重新创建?

楼主,继续讲解啊。正看的上瘾那。
 回复 引用 查看   
#14楼2009-12-22 10:27 | heye123      
楼主我想问下给出开始和结束时间的时间段,在时间段内的每天晚上11时59分59秒执行任务,我应该怎么写呢??

如:开始时间为2008-12-20,结束时间为2009-01-10,在开始时间和结束时间的时间段内每天晚上11时59分59秒执行,那应该怎么写????

 回复 引用 查看   
#15楼2010-02-17 20:08 |       
@Doho

lz说错了。quartz一样基于扫描,代码在:

quartzSchedulerThread。Run

Monitor.Wait(sigLock, 1000);

他的精度是1s,所以不支持毫秒级别的事件。

由于是扫描内存里面的schedule,所以性能比较高,不像扫描表结构这么恶心。


 回复 引用 查看   
#16楼2010-05-04 17:54 | ibo      
part 4和之后的内容呢,楼主请坚持,加油,兄弟姐妹们热切盼望!
 回复 引用 查看   
#17楼2010-08-10 11:09 | sun0201      
也是关于怎么修改触发器的问题,上面也有人问到,这个问题很严重了看来。
如果我原先触发了一个一定时间点上的触发器,现在用户把数据库里的触发时间更新了一下修改掉了,可事实我看过触发器还是在调用原来的触发器上设置的时间,就于实际不对了,就是没把这个触发器修改过来,不知道怎么去修改,在哪里修改。不能把这个触发器关联的调度给shutDown了,因为这个调度还包括其他很多的没修改的触发器呢,希望楼主帮忙了,这个太困扰我了,谢谢~!

 回复 引用 查看   
#18楼2010-09-17 16:11 | 李尚超      
应用示例
下面是用SimpleTrigger 和 CronTrigger 实现的触发配置面板:

这个源码,能不能放出来啊。我正需要了。

 回复 引用 查看   
#19楼2011-04-02 09:45 | aierong      
你好,你的blogs我仔细看了,收获不小 ,并且下载里面的一个例题QuartzExampleWin32.rar,该例题,运行后,并关闭,发现一个奇怪问题,怎么进程还存在!
请问您遇到这样事情吗?怎么解决的?

请看 下图