详细讲解Quartz.NET

Quartz.NET是一个开源的做业调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中。它提供了巨大的灵活性而不牺牲简单性。你可以用它来为执行一个做业而建立简单的或复杂的调度。它有不少特征,如:数据库支持,集群,插件,支持cron-like表达式等等。html

你曾经须要应用执行一个任务吗?这个任务天天或每周星期二晚上11:30,或许仅仅每月的最后一天执行。一个自动执行而无须干预的任务在执行过程当中若是发生一个严重错误,应用可以知到其执行失败并尝试从新执行吗?你和你的团队是用.NET编程吗?若是这些问题中任何一个你回答是,那么你应该使用Quartz.NET调度器。 Quartz.NET容许开发人员根据时间间隔(或天)来调度做业。它实现了做业和触发器的多对多关系,还能把多个做业与不一样的触发器关联。整合了 Quartz.NET的应用程序能够重用来自不一样事件的做业,还能够为一个事件组合多个做业.数据库

Quartz.NET入门express

要开始使用 Quartz.NET,须要用 Quartz.NET API 对项目进行配置。步骤以下:编程

1. 到http://quartznet.sourceforge.net/download.html下载 Quartz.NET API,最新版本是0.6安全

2. 解压缩Quartz.NET-0.6.zip 到目录,根据你的项目状况用Visual Studio 2003或者Visual Studio 2005打开相应工程,编译。你能够将它放进本身的应用中。Quartz.NET框架只须要少数的第三方库,而且这些三方库是必需的,你极可能已经在使用这些库了。多线程

3. 在Quartz.NET有一个叫作quartz.properties的配置文件,它容许你修改框架运行时环境。缺省是使用Quartz.dll里面的quartz.properties文件。固然你能够在应用程序配置文件中作相应的配置,下面是一个配置文件示例:架构

<?xml version="1.0" encoding="utf-8" ?>并发

<configuration>框架

<configSections>asp.net

<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />

</configSections>

<quartz>

<add key="quartz.scheduler.instanceName" value="ExampleDefaultQuartzScheduler" />

<add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />

<add key="quartz.threadPool.threadCount" value="10" />

<add key="quartz.threadPool.threadPriority" value="2" />

<add key="quartz.jobStore.misfireThreshold" value="60000" />

<add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />

</quartz>

</configuration>


为了方便读者,咱们使用Quartz.NET的例子代码来解释,如今来看一下 Quartz API 的主要组件。

调度器和做业

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 0.6 ,它是编写这篇文章时的稳定发行版)。

清单 1:做业

using System;

using System.Collections.Generic;

using System.Text;

using Common.Logging;

using Quartz;

namespace QuartzBeginnerExample

{

public class SimpleQuartzJob : IJob

{

private static ILog _log = LogManager.GetLogger(typeof(SimpleQuartzJob));

/// <summary>

/// Called by the <see cref="IScheduler" /> when a

/// <see cref="Trigger" /> fires that is associated with

/// the <see cref="IJob" />.

/// </summary>

public virtual void Execute(JobExecutionContext context)

{

try

{

// This job simply prints out its job name and the

// date and time that it is running

string jobName = context.JobDetail.FullName;

_log.Info("Executing job: " + jobName + " executing at " + DateTime.Now.ToString("r"));

}

catch (Exception e)

{

_log.Info("--- Error in job!");

JobExecutionException e2 = new JobExecutionException(e);

// this job will refire immediately

e2.RefireImmediately = true;

throw e2;

}

}

}

}


请注意,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 展现了触发器的基础:

清单2 SimpleTriggerRunner.cs

using System;

using System.Collections.Generic;

using System.Text;

using Common.Logging;

using Quartz;

using Quartz.Impl;

namespace QuartzBeginnerExample

{

public class SimpleTriggerRunner

{

public virtual void Run()

{

ILog log = LogManager.GetLogger(typeof(SimpleTriggerExample));

log.Info("------- Initializing -------------------");

// First we must get a reference to a scheduler

ISchedulerFactory sf = new StdSchedulerFactory();

IScheduler sched = sf.GetScheduler();

log.Info("------- Initialization Complete --------");

log.Info("------- Scheduling Jobs ----------------");

// jobs can be scheduled before sched.start() has been called

// get a "nice round" time a few seconds in the future...

DateTime ts = TriggerUtils.GetNextGivenSecondDate(null, 15);

// job1 will only fire once at date/time "ts"

JobDetail job = new JobDetail("job1", "group1", typeof(SimpleJob));

SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1");

// set its start up time

trigger.StartTime = ts;

// set the interval, how often the job should run (10 seconds here)

trigger.RepeatInterval = 10000;

// set the number of execution of this job, set to 10 times.

// It will run 10 time and exhaust.

trigger.RepeatCount = 100;

// schedule it to run!

DateTime ft = sched.ScheduleJob(job, trigger);

log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds",

job.FullName, ft.ToString("r"), trigger.RepeatCount, (trigger.RepeatInterval / 1000)));

log.Info("------- Starting Scheduler ----------------");

// All of the jobs have been added to the scheduler, but none of the jobs

// will run until the scheduler has been started

sched.Start();

log.Info("------- Started Scheduler -----------------");

log.Info("------- Waiting 30 seconds... --------------");

try

{

// wait 30 seconds to show jobs

Thread.Sleep(30 * 1000);

// executing...

}

catch (ThreadInterruptedException)

{

}

log.Info("------- Shutting Down ---------------------");

sched.Shutdown(true);

log.Info("------- Shutdown Complete -----------------");

// display some stats about the schedule that just ran

SchedulerMetaData metaData = sched.GetMetaData();

log.Info(string.Format("Executed {0} jobs.", metaData.NumJobsExecuted));

}

}

}


清单 2 开始时实例化一个 SchedulerFactory,得到此调度器。就像前面讨论过的,建立 JobDetail 对象时,它的构造函数要接受一个 Job 做为参数。顾名思义,SimpleTrigger 实例至关原始。在建立对象以后,设置几个基本属性以当即调度任务,而后每 10 秒重复一次,直到做业被执行 100 次。

还有其余许多方式能够操纵 SimpleTrigger。除了指定重复次数和重复间隔,还能够指定做业在特定日历时间执行,只需给定执行的最长时间或者优先级(稍后讨论)。执行的最长时间能够覆盖指定的重复次数,从而确保做业的运行不会超过最长时间。

清单 3 显示了 CronTrigger 的一个示例。请注意 SchedulerFactory、Scheduler 和 JobDetail 的实例化,与 SimpleTrigger 示例中的实例化是相同的。在这个示例中,只是修改了触发器。这里指定的 cron 表达式(“0/5 * * * * ?”)安排任务每 5 秒执行一次。

清单3 CronTriggerRunner.cs

using System;

using System.Collections.Generic;

using System.Text;

using Common.Logging;

using Quartz;

using Quartz.Impl;

using System.Threading;

namespace QuartzBeginnerExample

{

public class CronTriggerRunner

{

public virtual void Run()

{

ILog log = LogManager.GetLogger(typeof(CronTriggerRunner));

log.Info("------- Initializing -------------------");

// First we must get a reference to a scheduler

ISchedulerFactory sf = new StdSchedulerFactory();

IScheduler sched = sf.GetScheduler();

log.Info("------- Initialization Complete --------");

log.Info("------- Scheduling Jobs ----------------");

// jobs can be scheduled before sched.start() has been called

// job 1 will run every 20 seconds

JobDetail job = new JobDetail("job1", "group1", typeof(SimpleQuartzJob));

CronTrigger trigger = new CronTrigger("trigger1", "group1", "job1", "group1");

trigger.CronExpressionString = "0/20 * * * * ?";

sched.AddJob(job, true);

DateTime ft = sched.ScheduleJob(trigger);

log.Info(string.Format("{0} has been scheduled to run at: {1} and repeat based on expression: {2}", job.FullName, ft.ToString("r"), trigger.CronExpressionString));

log.Info("------- Starting Scheduler ----------------");

// All of the jobs have been added to the scheduler, but none of the

// jobs

// will run until the scheduler has been started

sched.Start();

log.Info("------- Started Scheduler -----------------");

log.Info("------- Waiting five minutes... ------------");

try

{

// wait five minutes to show jobs

Thread.Sleep(300 * 1000);

// executing...

}

catch (ThreadInterruptedException)

{

}

log.Info("------- Shutting Down ---------------------");

sched.Shutdown(true);

log.Info("------- Shutdown Complete -----------------");

SchedulerMetaData metaData = sched.GetMetaData();

log.Info(string.Format("Executed {0} jobs.", metaData.NumJobsExecuted));

}

}

}


如上所示,只用做业和触发器,就能访问大量的功能。可是,Quartz 是个丰富而灵活的调度包,对于愿意研究它的人来讲,它还提供了更多功能。下一节讨论 Quartz 的一些高级特性。

做业管理和存储

做业一旦被调度,调度器须要记住而且跟踪做业和它们的执行次数。若是你的做业是30分钟后或每30秒调用,这不是颇有用。事实上,做业执行须要很是准确和即时调用在被调度做业上的Execute()方法。Quartz经过一个称之为做业存储(JobStore)的概念来作做业存储和管理。

有效做业存储

Quartz提供两种基本做业存储类型。第一种类型叫作RAMJobStore,它利用一般的内存来持久化调度程序信息。这种做业存储类型最容易配置、构造和运行。Quartz.net缺省使用的就是RAMJobStore。对许多应用来讲,这种做业存储已经足够了。

然而,由于调度程序信息是存储在被分配在内存里面,因此,当应用程序中止运行时,全部调度信息将被丢失。若是你须要在从新启动之间持久化调度信息,则将须要第二种类型的做业存储。为了修正这个问题,Quartz.NET 提供了 AdoJobStore。顾名思义,做业仓库经过 ADO.NET把全部数据放在数据库中。数据持久性的代价就是性能下降和复杂性的提升。它将全部的数据经过ADO.NET保存到数据库可中。它的配置要比RAMJobStore稍微复杂,同时速度也没有那么快。可是性能的缺陷不是很是差,尤为是若是你在数据库表的主键上创建索引。

设置AdoJobStore

AdoJobStore几乎能够在任何数据库上工做,它普遍地使用Oracle, MySQL, MS SQLServer2000, HSQLDB, PostreSQL 以及 DB2。要使用AdoJobStore,首先必须建立一套Quartz使用的数据库表,能够在Quartz 的database\tables找到建立库表的SQL脚本。若是没有找到你的数据库类型的脚本,那么找到一个已有的,修改为为你数据库所须要的。须要注意的一件事情就是全部Quartz库表名都以QRTZ_做为前缀(例如:表"QRTZ_TRIGGERS",及"QRTZ_JOB_DETAIL")。实际上,能够你能够将前缀设置为任何你想要的前缀,只要你告诉AdoJobStore那个前缀是什么便可(在你的Quartz属性文件中配置)。对于一个数据库中使用多个scheduler实例,那么配置不一样的前缀能够建立多套库表,十分有用。

一旦数据库表已经建立,在配置和启动AdoJobStore以前,就须要做出一个更加剧要的决策。你要决定在你的应用中须要什么类型的事务。若是不想将scheduling命令绑到其余的事务上,那么你能够经过对JobStore使用JobStoreTX来让Quartz帮你管理事务(这是最广泛的选择)。

最后的疑问就是如何创建得到数据库联接的数据源(DataSource)。Quartz属性中定义数据源是经过提供全部联接数据库的信息,让Quartz本身建立和管理数据源。

要使用AdoJobStore(假定使用StdSchedulerFactory),首先须要设置Quartz配置中的quartz.jobStore.type属性为Quartz.Impl.AdoJobStore.JobStoreTX, Quartz。

配置 Quartz使用 JobStoreTx

quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz

下一步,须要为JobStore 选择一个DriverDelegate , DriverDelegate负责作指定数据库的全部ADO.NET工做。StdADO.NETDelegate是一个使用vanilla" ADO.NET代码(以及SQL语句)来完成工做的代理。若是数据库没有其余指定的代理,那么就试用这个代理。只有当使用StdADO.NETDelegate发生问题时,咱们才会使用数据库特定的代理(这看起来很是乐观。其余的代理能够在Quartz.Impl.AdoJobStor命名空间找到。)。其余的代理包括PostgreSQLDelegate ( 专为PostgreSQL 7.x)。

一旦选择好了代理,就将它的名字设置给AdoJobStore。

配置AdoJobStore 使用DriverDelegate

quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz

接下来,须要为JobStore指定所使用的数据库表前缀(前面讨论过)。

配置AdoJobStore的数据库表前缀

quartz.jobStore.tablePrefix = QRTZ

而后须要设置JobStore所使用的数据源。必须在Quartz属性中定义已命名的数据源,好比,咱们指定Quartz使用名为"default"的数据源(在配置文件的其余地方定义)。

配置 AdoJobStore使用数据源源的名字

properties["quartz.jobStore.dataSource"] = "default"

最后,须要配置数据源的使用的Ado.net数据提供者和数据库链接串,数据库链接串是标准的Ado.net 数据库链接的链接串。数据库提供者是关系数据库同Quartz.net之间保持低耦合的数据库的链接提供者.

配置AdoJobStore使用数据源源的数据库链接串和数据库提供者

quartz.dataSource.default.connectionString = Server=(local);Database=quartz;Trusted_Connection=True;

quartz.dataSource.default.provider= SqlServer-11

目前Quartz.net支持的如下数据库的数据提供者:

l SqlServer-11 - SQL Server driver for .NET Framework 1.1

l SqlServer-20 - SQL Server driver for .NET Framework 2.0

l OracleClient-20 - Microsoft's Oracle Driver (comes bundled with .NET Framework)

l OracleODP-20 - Oracle's Oracle Driver

l MySql-10 - MySQL Connector/.NET v. 1.0.7

l MySql-109 - MySQL Connector/.NET v. 1.0.9

l MySql-50 - MySQL Connector/.NET v. 5.0 (.NET 2.0)

l MySql-51 - MySQL Connector/:NET v. 5.1 (.NET 2.0)

l SQLite1044 - SQLite ADO.NET 2.0 Provider v. 1.0.44 (.NET 2.0)

若是Scheduler很是忙(好比,执行的任务数量差很少和线程池的数量相同,那么你须要正确地配置DataSource的链接数量为线程池数量。为了指示AdoJobStore全部的JobDataMaps中的值都是字符串,而且能以“名字-值”对的方式存储而不是以复杂对象的序列化形式存储在BLOB字段中,应设置 quartz.jobStore.usePropertiess配置参数的值为"true"(这是缺省的方式)。这样作,从长远来看很是安全,这样避免了对存储在BLOB中的非字符串的序列化对象的类型转换问题。

清单 4 展现了 AdoJobStore提供的数据持久性。就像在前面的示例中同样,先从初始化 SchedulerFactory 和 Scheduler 开始。而后,再也不须要初始化做业和触发器,而是要获取触发器群组名称列表,以后对于每一个群组名称,获取触发器名称列表。请注意,每一个现有的做业都应当用 Scheduler. RescheduleJob () 方法从新调度。仅仅从新初始化在先前的应用程序运行时终止的做业,不会正确地装载触发器的属性。

清单4 AdoJobStoreRunner.cs

public class AdoJobStoreRunner : IExample

{

public string Name

{

get { return GetType().Name; }

}

private static ILog _log = LogManager.GetLogger(typeof(AdoJobStoreRunner));

public virtual void CleanUp(IScheduler inScheduler)

{

_log.Warn("***** Deleting existing jobs/triggers *****");

// unschedule jobs

string[] groups = inScheduler.TriggerGroupNames;

for (int i = 0; i < groups.Length; i++)

{

String[] names = inScheduler.GetTriggerNames(groups[i]);

for (int j = 0; j < names.Length; j++)

inScheduler.UnscheduleJob(names[j], groups[i]);

}

// delete jobs

groups = inScheduler.JobGroupNames;

for (int i = 0; i < groups.Length; i++)

{

String[] names = inScheduler.GetJobNames(groups[i]);

for (int j = 0; j < names.Length; j++)

inScheduler.DeleteJob(names[j], groups[i]);

}

}

public virtual void Run(bool inClearJobs, bool inScheduleJobs)

{

NameValueCollection properties = new NameValueCollection();

properties["quartz.scheduler.instanceName"] = "TestScheduler";

properties["quartz.scheduler.instanceId"] = "instance_one";

properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";

properties["quartz.threadPool.threadCount"] = "5";

properties["quartz.threadPool.threadPriority"] = "Normal";

properties["quartz.jobStore.misfireThreshold"] = "60000";

properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";

properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz";

properties["quartz.jobStore.useProperties"] = "false";

properties["quartz.jobStore.dataSource"] = "default";

properties["quartz.jobStore.tablePrefix"] = "QRTZ_";

properties["quartz.jobStore.clustered"] = "true";

// if running MS SQL Server we need this

properties["quartz.jobStore.selectWithLockSQL"] = "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = @lockName";

properties["quartz.dataSource.default.connectionString"] = @"Server=LIJUNNIN-PCSQLEXPRESS;Database=quartz;Trusted_Connection=True;";

properties["quartz.dataSource.default.provider"] = "SqlServer-20";

// First we must get a reference to a scheduler

ISchedulerFactory sf = new StdSchedulerFactory(properties);

IScheduler sched = sf.GetScheduler();

if (inClearJobs)

{

CleanUp(sched);

}

_log.Info("------- Initialization Complete -----------");

if (inScheduleJobs)

{

_log.Info("------- Scheduling Jobs ------------------");

string schedId = sched.SchedulerInstanceId;

int count = 1;

JobDetail job = new JobDetail("job_" + count, schedId, typeof(SimpleQuartzJob));

// ask scheduler to re-Execute this job if it was in progress when

// the scheduler went down...

job.RequestsRecovery = true;

SimpleTrigger trigger = new SimpleTrigger("trig_" + count, schedId, 20, 5000L);

trigger.StartTime = DateTime.Now.AddMilliseconds(1000L);

sched.ScheduleJob(job, trigger);

_log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job.FullName, trigger.GetNextFireTime(), trigger.RepeatCount, (trigger.RepeatInterval / 1000)));

count++;

job = new JobDetail("job_" + count, schedId, typeof(SimpleQuartzJob));

// ask scheduler to re-Execute this job if it was in progress when

// the scheduler went down...

job.RequestsRecovery = (true);

trigger = new SimpleTrigger("trig_" + count, schedId, 20, 5000L);

trigger.StartTime = (DateTime.Now.AddMilliseconds(2000L));

sched.ScheduleJob(job, trigger);

_log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job.FullName, trigger.GetNextFireTime(), trigger.RepeatCount, (trigger.RepeatInterval / 1000)));

count++;

job = new JobDetail("job_" + count, schedId, typeof(SimpleQuartzJob));

// ask scheduler to re-Execute this job if it was in progress when

// the scheduler went down...

job.RequestsRecovery = (true);

trigger = new SimpleTrigger("trig_" + count, schedId, 20, 3000L);

trigger.StartTime = (DateTime.Now.AddMilliseconds(1000L));

sched.ScheduleJob(job, trigger);

_log.Info(string.Format("{0} will run at: {1} and repeat: {2} times, every {3} seconds", job.FullName, trigger.GetNextFireTime(), trigger.RepeatCount, (trigger.RepeatInterval / 1000)));

count++;

job = new JobDetail("job_" + count, schedId, typeof(SimpleQuartzJob));

// ask scheduler to re-Execute this job if it was in progress when

// the scheduler went down...

job.RequestsRecovery = (true);

trigger = new SimpleTrigger("trig_" + count, schedId, 20, 4000L);

trigger.StartTime = (DateTime.Now.AddMilliseconds(1000L));

sched.ScheduleJob(job, trigger);

_log.Info(string.Format("{0} will run at: {1} & repeat: {2}/{3}", job.FullName, trigger.GetNextFireTime(), trigger.RepeatCount, trigger.RepeatInterval));

count++;

job = new JobDetail("job_" + count, schedId, typeof(SimpleQuartzJob));

// ask scheduler to re-Execute this job if it was in progress when

// the scheduler went down...

job.RequestsRecovery = (true);

trigger = new SimpleTrigger("trig_" + count, schedId, 20, 4500L);

trigger.StartTime = (DateTime.Now.AddMilliseconds(1000L));

sched.ScheduleJob(job, trigger);

_log.Info(string.Format("{0} will run at: {1} & repeat: {2}/{3}", job.FullName, trigger.GetNextFireTime(), trigger.RepeatCount, trigger.RepeatInterval));

}

// jobs don't start firing until start() has been called...

_log.Info("------- Starting Scheduler ---------------");

sched.Start();

_log.Info("------- Started Scheduler ----------------");

_log.Info("------- Waiting for one hour... ----------");

Thread.Sleep(TimeSpan.FromHours(1));

_log.Info("------- Shutting Down --------------------");

sched.Shutdown();

_log.Info("------- Shutdown Complete ----------------");

}

public void Run()

{

bool clearJobs = true;

bool scheduleJobs = true;

AdoJobStoreRunner example = new AdoJobStoreRunner();

example.Run(clearJobs, scheduleJobs);

}

}


结束语

Quartz.net 做业调度框架所提供的 API 在两方面都表现极佳:既全面强大,又易于使用。Quartz 能够用于简单的做业触发,也能够用于复杂的 Ado.net持久的做业存储和执行。

示例下载

http://www.cnblogs.com/Files/shanyou/QuartzBeginnerExample.zip