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 using System;
2 using System.Collections.Specialized;
3 using System.Threading;
4 using Common.Logging;
5 using Quartz;
6 using Quartz.Impl;
7 using Quartz.Job;
8 using System.Windows.Forms;
9 namespace QuartzDemo
10 {
11 /// <summary>
12 /// 無狀態的可恢復的任務
13 /// </summary>
14 public class RecoveryJob : IJob
15 {
16
17 private const string Count = "count";
18 public virtual void Execute(IJobExecutionContext context)
19 {
20
21 JobKey jobKey = context.JobDetail.Key;
22 if (isOpen("FrmConsole"))
23 {
24 try
25 {
26 //獲取當前Form1實例
27 __instance = (FrmConsole)Application.OpenForms["FrmConsole"];
28 // 如果任務是恢復的任務的話
29 if (context.Recovering)
30 {
31 __instance.SetInfo(string.Format("{0} RECOVERING at {1}", jobKey, DateTime.Now.ToString("r")));
32 }
33 else
34 {
35 __instance.SetInfo(string.Format("{0} starting at {1}", jobKey, DateTime.Now.ToString("r")));
36 }
37
38 JobDataMap data = context.JobDetail.JobDataMap;
39 int count;
40 if (data.ContainsKey(Count))
41 {
42 //是否能從數據庫中恢復,如果保存Job等信息的話,程序運行突然終端(可用調試時中斷運行,而不是關閉窗體來模擬)
43 count = data.GetInt(Count);
44 }
45 else
46 {
47 count = 0;
48 }
49 count++;
50 data.Put(Count, count);
51
52 __instance.SetInfo(string.Format(" {0} Count #{1}", jobKey, count));
53 }
54 catch (Exception ex)
55 {
56 Console.WriteLine(ex.Message);
57 }
58 }
59 }
60
61
62 private static FrmConsole __instance = null;
63
64 /// <summary>
65 /// 判斷窗體是否打開
66 /// </summary>
67 /// <param name="appName"></param>
68 /// <returns></returns>
69 private bool isOpen(string appName)
70 {
71 FormCollection collection = Application.OpenForms;
72 foreach (Form form in collection)
73 {
74 if (form.Name == appName)
75 {
76 return true;
77 }
78 }
79 return false;
80 }
81
82 }
83 }
是一個有狀態的任務,和無狀態的區別就是在任務類的上面用[PersistJobDataAfterExecution]標注任務是有狀態的 , 有狀態的任務不允許並發執行,也需要標注 [DisallowConcurrentExecution],代碼如下:
1 using System;
2 using System.Collections.Specialized;
3 using System.Threading;
4 using Common.Logging;
5 using Quartz;
6 using Quartz.Impl;
7 using Quartz.Job;
8 using System.Windows.Forms;
9 namespace QuartzDemo
10 {
11 /// <summary>
12 /// 用這個[PersistJobDataAfterExecution]標注任務是有狀態的,
13 /// 有狀態的任務不允許並發執行 [DisallowConcurrentExecution]
14 /// </summary>
15 [PersistJobDataAfterExecution]
16 [DisallowConcurrentExecution]
17 public class RecoveryStatefulJob : RecoveryJob
18 {
19
20 }
21 }
用 properties["quartz.dataSource.default.connectionString"] = "Server=(local);Database=QRTZ_;Trusted_Connection=True;";定義了數據庫的連接信息,程序運行時會自動將任務保存到數據庫中:
1 using System;
2 using System.Collections.Specialized;
3 using System.Threading;
4 using Common.Logging;
5 using Quartz;
6 using Quartz.Impl;
7 using Quartz.Job;
8 using System.Windows.Forms;
9 namespace QuartzDemo
10 {
11 /// <summary>
12 /// AdoJobStore的用法示例
13 /// </summary>
14 public class AdoJobStoreExample
15 {
16 public virtual void Run(bool inClearJobs, bool inScheduleJobs)
17 {
18 NameValueCollection properties = new NameValueCollection();
19
20 properties["quartz.scheduler.instanceName"] = "TestScheduler";
21 properties["quartz.scheduler.instanceId"] = "instance_one";
22 properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
23 properties["quartz.threadPool.threadCount"] = "5";
24 properties["quartz.threadPool.threadPriority"] = "Normal";
25 properties["quartz.jobStore.misfireThreshold"] = "60000";
26 properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
27 properties["quartz.jobStore.useProperties"] = "false";
28 properties["quartz.jobStore.dataSource"] = "default";
29 properties["quartz.jobStore.tablePrefix"] = "QRTZ_";
30 properties["quartz.jobStore.clustered"] = "true";
31 // SQLite
32 // properties["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz";
33 properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";
34 // 數據庫連接字符串
35 properties["quartz.dataSource.default.connectionString"] = "Server=(local);Database=QRTZ_;Trusted_Connection=True;";
36 properties["quartz.dataSource.default.provider"] = "SqlServer-20";
37
38 // First we must get a reference to a scheduler
39 ISchedulerFactory sf = new StdSchedulerFactory(properties);
40 IScheduler sched = sf.GetScheduler();
41
42 bool b是否恢復 = false;
43 if (inClearJobs)
44 {
45 Console.WriteLine("***** Deleting existing jobs/triggers *****");
46 // sched.Clear();
47 }
48
49
50 if (inScheduleJobs)
51 {
52
53 string schedId = sched.SchedulerInstanceId;
54
55 int count = 1;
56
57 //定義一個無狀態的任務
58 IJobDetail job = JobBuilder.Create<RecoveryJob>()
59 .WithIdentity("recoveryjob_" + count, schedId)
60 .RequestRecovery() //recovery
61 .Build();
62
63
64 ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create()
65 .WithIdentity("triger_" + count, schedId)
66 .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second))
67 .WithSimpleSchedule(x => x.WithRepeatCount(20).WithInterval(TimeSpan.FromSeconds(3)))
68 .Build();
69 //可用此來查看定義的觸發器觸發規則
70 //log.InfoFormat("{0} will run at: {1} and repeat: {2} times, every {3} seconds",
71 //job.Key, trigger.GetNextFireTimeUtc(),
72 //trigger.RepeatCount,
73 //trigger.RepeatInterval.TotalSeconds);
74 try
75 {
76 //如果數據庫已經存在同名job和trigger,則綁定失敗
77 sched.ScheduleJob(job, trigger);
78 }
79 catch
80 {
81 b是否恢復 = true;
82 }
83 count++;
84
85 //定義一個有狀態的任務***********************************************************
86 job = JobBuilder.Create<RecoveryStatefulJob>()
87 .WithIdentity("Statefuljob_" + count, schedId)
88 .RequestRecovery() // recovery
89 .Build();
90
91 trigger = (ISimpleTrigger)TriggerBuilder.Create()
92 .WithIdentity("triger_" + count, schedId)
93 .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second))
94 .WithSimpleSchedule(x => x.WithRepeatCount(20).WithInterval(TimeSpan.FromSeconds(3)))
95 .Build();
96
97 try
98 {
99 sched.ScheduleJob(job, trigger);
100 }
101 catch
102 {
103 b是否恢復 = true;
104 }
105
106
107
108 }
109
110 //啟動
111 sched.Start();
112 //sched.Shutdown();
113
114 }
115
116 public string Name
117 {
118 get { return GetType().Name; }
119 }
120
121 public void Run()
122 {
123 bool clearJobs = true;
124 //clearJobs = false;
125 bool scheduleJobs = true;
126 AdoJobStoreExample example = new AdoJobStoreExample();
127 example.Run(clearJobs, scheduleJobs);
128 }
129 }
130 }

可以看到有狀態的計數每次累加1,而無狀態的每次執行時都會丟失累加數(新的實例),中斷程序,查看數據庫的QRTZ_JOB_DETAILS表,可以看見還有一個持久化的任務:

中斷程序後(調試狀態時不關閉窗體,而是中斷調試,模擬異常關閉) ,再重新運行可以看到如下界面:
