Quartz.NET框架的核心是调度器。调度器负责管理Quartz.NET应用运行时环境。调度器不是靠自己做所有的工作,而是依赖框架内一些非常重要的部件。Quartz不仅仅是线程和线程管理。为确保可伸缩性,Quartz.NET采用了基于多线程的架构。 启动时,框架初始化一套worker线程,这套线程被调度器用来执行预定的作业。这就是Quartz.NET怎样能并发运行多个作业的原理。 Quartz.NET依赖一套松耦合的线程池管理部件来管理线程环境。作业是一个执行任务的简单.NET类。任务可以是任何C#\VB.NET代码。只需你实现Quartz.IJob接口并且在出现严重错误情况下抛出JobExecutionException异常即可。
IJob接口包含唯一的一个方法Execute(),作业从这里开始执行。一旦实现了IJob接口和Execute ()方法,当Quartz.NET确定该是作业运行的时候,它将调用你的作业。Execute()方法内就完全是你要做的事情。
通过实现 Quartz.IJob接口,可以使 .NET 类变成可执行的。清单 1 提供了 Quartz.IJob作业的一个示例。这个类用一条非常简单的输出语句覆盖了 Execute(JobExecutionContext context) 方法。这个方法可以包含我们想要执行的任何代码(所有的代码示例都基于 Quartz.NET ,它是编写这篇文章时的稳定发行版)。
清单 1:作业
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using Common.Logging;
5 using Quartz;
6
7 namespace QuartzBeginnerExample
8 {
9 public class SimpleQuartzJob : IJob
10 {
11
12 private static ILog _log = LogManager.GetLogger(typeof(SimpleQuartzJob));
13
14 /// <summary>
15 /// Called by the <see cref="IScheduler" /> when a
16 /// <see cref="Trigger" /> fires that is associated with
17 /// the <see cref="IJob" />.
18 /// </summary>
19 public virtual void Execute(JobExecutionContext context)
20 {
21 try
22 {
23 // This job simply prints out its job name and the
24 // date and time that it is running
25 string jobName = context.JobDetail.FullName;
26 _log.Info("Executing job: " + jobName + " executing at " + DateTime.Now.ToString("r"));
27 }
28 catch (Exception e)
29 {
30 _log.Info("--- Error in job!");
31 JobExecutionException e2 = new JobExecutionException(e);
32 // this job will refire immediately
33 e2.RefireImmediately = true;
34 throw e2;
35 }
36 }
37 }
38 }
39
40
请注意,Execute 方法接受一个 JobExecutionContext 对象作为参数。这个对象提供了作业实例的运行时上下文。特别地,它提供了对调度器和触发器的访问,这两者协作来启动作业以及作业的 JobDetail 对象的执行。Quartz.NET 通过把作业的状态放在 JobDetail 对象中并让 JobDetail 构造函数启动一个作业的实例,分离了作业的执行和作业周围的状态。JobDetail 对象储存作业的侦听器、群组、数据映射、描述以及作业的其他属性
作业和触发器:
Quartz.NET设计者做了一个设计选择来从调度分离开作业。Quartz.NET中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是SimpleTrigger和CronTrigger。SimpleTrigger为需要简单打火调度而设计。
典型地,如果你需要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个作业,那么SimpleTrigger适合你。另一方面,如果你有许多复杂的作业调度,那么或许需要CronTrigger。
CronTrigger是基于Calendar-like调度的。当你需要在除星期六和星期天外的每天上午10点半执行作业时,那么应该使用CronTrigger。正如它的名字所暗示的那样,CronTrigger是基于Unix克隆表达式的。
Cron表达式被用来配置CronTrigger实例。Cron表达式是一个由7个子表达式组成的字符串。每个子表达式都描述了一个单独的日程细节。这些子表达式用空格分隔,分别表示:
1. Seconds 秒
2. Minutes 分钟
3. Hours 小时
4. Day-of-Month 月中的天
5. Month 月
6. Day-of-Week 周中的天
7. Year (optional field) 年(可选的域)
一个cron表达式的例子字符串为"0 0 12 ? * WED",这表示“每周三的中午12:00”。
单个子表达式可以包含范围或者列表。例如:前面例子中的周中的天这个域(这里是"WED")可以被替换为"MON-FRI", "MON, WED, FRI"或者甚至"MON-WED,SAT"。
通配符(''*'')可以被用来表示域中“每个”可能的值。因此在"Month"域中的*表示每个月,而在Day-Of-Week域中的*则表示“周中的每一天”。
所有的域中的值都有特定的合法范围,这些值的合法范围相当明显,例如:秒和分域的合法值为0到59,小时的合法范围是0到23,Day-of-Month中值得合法凡范围是0到31,但是需要注意不同的月份中的天数不同。月份的合法值是0到11。或者用字符串JAN,FEB MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 及DEC来表示。Days-of-Week可以用1到7来表示(1=星期日)或者用字符串SUN, MON, TUE, WED, THU, FRI 和SAT来表示.
''/''字符用来表示值的增量,例如, 如果分钟域中放入''0/15'',它表示“每隔15分钟,从0开始”,如果在份中域中使用''3/20'',则表示“小时中每隔20分钟,从第3分钟开始”或者另外相同的形式就是''3,23,43''。
''?''字符可以用在day-of-month及day-of-week域中,它用来表示“没有指定值”。这对于需要指定一个或者两个域的值而不需要对其他域进行设置来说相当有用。
''L'' 字符可以在day-of-month及day-of-week中使用,这个字符是"last"的简写,但是在两个域中的意义不同。例如,在day-of- month域中的"L"表示这个月的最后一天,即,一月的31日,非闰年的二月的28日。如果它用在day-of-week中,则表示"7"或者"SAT"。但是如果在day-of-week域中,这个字符跟在别的值后面,则表示"当月的最后的周XXX"。例如:"6L" 或者 "FRIL"都表示本月的最后一个周五。当使用''L''选项时,最重要的是不要指定列表或者值范围,否则会导致混乱。
''W'' 字符用来指定距离给定日最接近的周几(在day-of-week域中指定)。例如:如果你为day-of-month域指定为"15W",则表示“距离月中15号最近的周几”。
''#''表示表示月中的第几个周几。例如:day-of-week域中的"6#3" 或者 "FRI#3"表示“月中第三个周五”。
作为一个例子,下面的Quartz.NET克隆表达式将在星期一到星期五的每天上午10点15分执行一个作业。
0 15 10 ? * MON-FRI
下面的表达式
0 15 10 ? * 6L 2007-2010
将在2007年到2010年的每个月的最后一个星期五上午10点15分执行作业。你不可能用SimpleTrigger来做这些事情。你可以用两者之中的任何一个,但哪个跟合适则取决于你的调度需要。
清单 2 中的 SimpleTrigger 展示了触发器的基础:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using Common.Logging;
5 using Quartz;
6 using Quartz.Impl;
7 using System.Threading;
8
9 namespace QuartzBeginnerExample
10 {
11 public class SimpleTriggerRunner : IExample
12 {
13 public string Name
14 {
15 get { return GetType().Name; }
16 }
17
18 public virtual void Run()
19 {
20 ILog log = LogManager.GetLogger(typeof(SimpleTriggerRunner));
21
22 log.Info("------- Initializing -------------------");
23
24 // First we must get a reference to a scheduler
25 ISchedulerFactory sf = new StdSchedulerFactory();
26 IScheduler sched = sf.GetScheduler();
27
28 log.Info("------- Initialization Complete --------");
29
30 log.Info("------- Scheduling Jobs ----------------");
31
32 // jobs can be scheduled before sched.start() has been called
33
34 // get a "nice round" time a few seconds in the future...
35 DateTime ts = TriggerUtils.GetNextGivenSecondDate(null, 15);
36
37 // job1 will only fire once at date/time "ts"
38 JobDetail job = new JobDetail("job1", "group1", typeof(SimpleQuartzJob));
39 SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1");
40 // set its start up time
41 trigger.StartTime = ts;
42 // set the interval, how often the job should run (10 seconds here)
43 trigger.RepeatInterval = 10000;
44 // set the number of execution of this job, set to 10 times.
45 // It will run 10 time and exhaust.
46 trigger.RepeatCount = 100;
47
48
49 // schedule it to run!
50 DateTime ft = sched.ScheduleJob(job, trigger);
51 log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds",
52 job.FullName, ft.ToString("r"), trigger.RepeatCount, (trigger.RepeatInterval / 1000)));
53
54
55 log.Info("------- Starting Scheduler ----------------");
56
57 // All of the jobs have been added to the scheduler, but none of the jobs
58 // will run until the scheduler has been started
59 sched.Start();
60 log.Info("------- Started Scheduler -----------------");
61
62 log.Info("------- Waiting five minutes... ------------");
63 try
64 {
65 // wait five minutes to show jobs
66 Thread.Sleep(300 * 1000);
67 // executing...
68 }
69 catch (ThreadInterruptedException)
70 {
71 }
72
73 log.Info("------- Shutting Down ---------------------");
74
75 sched.Shutdown(true);
76
77 log.Info("------- Shutdown Complete -----------------");
78
79 // display some stats about the schedule that just ran
80 SchedulerMetaData metaData = sched.GetMetaData();
81 log.Info(string.Format("Executed {0} jobs.", metaData.NumJobsExecuted));
82 }
83 }
84 }
85
清单 2 开始时实例化一个 SchedulerFactory,获得此调度器。就像前面讨论过的,创建 JobDetail 对象时,它的构造函数要接受一个 Job 作为参数。顾名思义,SimpleTrigger 实例相当原始。在创建对象之后,设置几个基本属性以立即调度任务,然后每 10 秒重复一次,直到作业被执行 100 次。
还有其他许多方式可以操纵 SimpleTrigger。除了指定重复次数和重复间隔,还可以指定作业在特定日历时间执行,只需给定执行的最长时间或者优先级(稍后讨论)。执行的最长时间可以 覆盖指定的重复次数,从而确保作业的运行不会超过最长时间。