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表,可以看見還有一個持久化的任務:
中斷程序後(調試狀態時不關閉窗體,而是中斷調試,模擬異常關閉) ,再重新運行可以看到如下界面: