Quartz.NET 任务调度的核心元素是 scheduler, trigger 和 job,其中 trigger(用于定义调度时间的元素,即按照什么时间规则去执行任务) 和 job 是任务调度的元数据,scheduler 是实际执行调度的控制器。在Quartz.NET中主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。某些任务需要对数据库中的数据进行增删改处理 , 这些任务不能并发执行,就需要用到无状态的任务 , 否则会造成数据混乱。
另外有些情况下,我们需要将任务保存到数据库中,特别是有些任务中包含参数,例如累加的任务,如果可以保存到数据库中,即便中间断电或者程序异常重启,中间计算的结果也不会丢失,可以从断点的结果进行运算(首先恢复任务),下面介绍一下如何用AdoJobStore将任务保存到SQL Server数据库中.
事先要在数据库上新建一个QRTZ_数据库,并执行SQL建表脚本:
1 RecoveryJob
是一个无状态的任务,代码如下:
usingSystem; usingSystem.Collections.Specialized; usingSystem.Threading; usingCommon.Logging; usingQuartz; usingQuartz.Impl; usingQuartz.Job; usingSystem.Windows.Forms; namespaceQuartzDemo{ /// <summary>/// 无状态的可恢复的任务/// </summary>publicclassRecoveryJob : IJob { privateconststringCount="count"; publicvirtualvoidExecute(IJobExecutionContextcontext) { JobKeyjobKey=context.JobDetail.Key; if (isOpen("FrmConsole")) { try { //获取当前Form1实例__instance= (FrmConsole)Application.OpenForms["FrmConsole"]; // 如果任务是恢复的任务的话if (context.Recovering) { __instance.SetInfo(string.Format("{0} RECOVERING at {1}", jobKey, DateTime.Now.ToString("r"))); } else { __instance.SetInfo(string.Format("{0} starting at {1}", jobKey, DateTime.Now.ToString("r"))); } JobDataMapdata=context.JobDetail.JobDataMap; intcount; if (data.ContainsKey(Count)) { //是否能从数据库中恢复,如果保存Job等信息的话,程序运行突然终端(可用调试时中断运行,而不是关闭窗体来模拟)count=data.GetInt(Count); } else { count=0; } count++; data.Put(Count, count); __instance.SetInfo(string.Format(" {0} Count #{1}", jobKey, count)); } catch (Exceptionex) { Console.WriteLine(ex.Message); } } } privatestaticFrmConsole__instance=null; /// <summary>/// 判断窗体是否打开/// </summary>/// <param name="appName"></param>/// <returns></returns>privateboolisOpen(stringappName) { FormCollectioncollection=Application.OpenForms; foreach (Formformincollection) { if (form.Name==appName) { returntrue; } } returnfalse; } } }
2 RecoveryStatefulJob
是一个有状态的任务,和无状态的区别就是在任务类的上面用[PersistJobDataAfterExecution]标注任务是有状态的 , 有状态的任务不允许并发执行,也需要标注 [DisallowConcurrentExecution],代码如下:
usingSystem; usingSystem.Collections.Specialized; usingSystem.Threading; usingCommon.Logging; usingQuartz; usingQuartz.Impl; usingQuartz.Job; usingSystem.Windows.Forms; namespaceQuartzDemo{ /// <summary>/// 用这个[PersistJobDataAfterExecution]标注任务是有状态的,/// 有状态的任务不允许并发执行 [DisallowConcurrentExecution]/// </summary> [PersistJobDataAfterExecution] [DisallowConcurrentExecution] publicclassRecoveryStatefulJob : RecoveryJob { } }
3 AdoJobStoreExample
用 properties["quartz.dataSource.default.connectionString"] = "Server=(local);Database=QRTZ_;Trusted_Connection=True;";定义了数据库的连接信息,程序运行时会自动将任务保存到数据库中:
usingSystem; usingSystem.Collections.Specialized; usingSystem.Threading; usingCommon.Logging; usingQuartz; usingQuartz.Impl; usingQuartz.Job; usingSystem.Windows.Forms; namespaceQuartzDemo{ /// <summary>/// AdoJobStore的用法示例/// </summary>publicclassAdoJobStoreExample { publicvirtualvoidRun(boolinClearJobs, boolinScheduleJobs) { NameValueCollectionproperties=newNameValueCollection(); 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.useProperties"] ="false"; properties["quartz.jobStore.dataSource"] ="default"; properties["quartz.jobStore.tablePrefix"] ="QRTZ_"; properties["quartz.jobStore.clustered"] ="true"; // SQLite// properties["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz";properties["quartz.jobStore.driverDelegateType"] ="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz"; // 数据库连接字符串properties["quartz.dataSource.default.connectionString"] ="Server=(local);Database=QRTZ_;Trusted_Connection=True;"; properties["quartz.dataSource.default.provider"] ="SqlServer-20"; // First we must get a reference to a schedulerISchedulerFactorysf=newStdSchedulerFactory(properties); ISchedulersched=sf.GetScheduler(); boolb是否恢复=false; if (inClearJobs) { Console.WriteLine("***** Deleting existing jobs/triggers *****"); // sched.Clear(); } if (inScheduleJobs) { stringschedId=sched.SchedulerInstanceId; intcount=1; //定义一个无状态的任务IJobDetailjob=JobBuilder.Create<RecoveryJob>() .WithIdentity("recoveryjob_"+count, schedId) .RequestRecovery() //recovery .Build(); ISimpleTriggertrigger= (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("triger_"+count, schedId) .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second)) .WithSimpleSchedule(x=>x.WithRepeatCount(20).WithInterval(TimeSpan.FromSeconds(3))) .Build(); //可用此来查看定义的触发器触发规则//log.InfoFormat("{0} will run at: {1} and repeat: {2} times, every {3} seconds",//job.Key, trigger.GetNextFireTimeUtc(),//trigger.RepeatCount,//trigger.RepeatInterval.TotalSeconds);try { //如果数据库已经存在同名job和trigger,则绑定失败sched.ScheduleJob(job, trigger); } catch { b是否恢复=true; } count++; //定义一个有状态的任务***********************************************************job=JobBuilder.Create<RecoveryStatefulJob>() .WithIdentity("Statefuljob_"+count, schedId) .RequestRecovery() // recovery .Build(); trigger= (ISimpleTrigger)TriggerBuilder.Create() .WithIdentity("triger_"+count, schedId) .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second)) .WithSimpleSchedule(x=>x.WithRepeatCount(20).WithInterval(TimeSpan.FromSeconds(3))) .Build(); try { sched.ScheduleJob(job, trigger); } catch { b是否恢复=true; } } //启动sched.Start(); //sched.Shutdown(); } publicstringName { get { returnGetType().Name; } } publicvoidRun() { boolclearJobs=true; //clearJobs = false;boolscheduleJobs=true; AdoJobStoreExampleexample=newAdoJobStoreExample(); example.Run(clearJobs, scheduleJobs); } } }
4 效果
执行演示程序,具体的界面如下所示:
其中可以发现有状态的任务和无状态的区别,有状态的任务可以在不同的任务中共享变量,而无状态的则是隔离的,不能共享变量。可以看到有状态的计数每次累加1,而无状态的每次执行时都会丢失累加数(新的实例),中断程序,查看数据库的QRTZ_JOB_DETAILS表,可以看见还有一个持久化的任务:
中断程序后(调试状态时不关闭窗体,而是中断调试,模拟异常关闭) ,再重新运行可以看到如下界面: