當學完第二課之後,你欣喜的發現,讓jobs工作起來是還是相當簡單的。雖然讓jobs運行起來很簡單,對於其執行的關鍵內容還是需要知道的。它們是IJob接口中的Execute和JobDetails。
當你定義一個實現IJob接口的類的時候,你需要在裡面實現實際需要執行的代碼。Quartz.NET需要知道關於這代碼的各種信息,這樣 Quartz.NET才能像你期望的那樣工作。這些細節是在JobDetail類中進行了描述,在上一節以及進行了簡單的描述。
JobDetail由JobBuilder進行實例化的。JobBuilder容許開發人員使用 fluent interface.進行自定義JobDetail細節。
讓我們花點時間看Job的機制以及在Quartz.NET中的生命周期。在第一節中已經看到的例子讓我們再看一遍:
1 // define the job and tie it to our HelloJob class 2 IJobDetail job = JobBuilder.Create<HelloJob>() 3 .WithIdentity("myJob", "group1") 4 .Build(); 5 6 // Trigger the job to run now, and then every 40 seconds 7 ITrigger trigger = TriggerBuilder.Create() 8 .WithIdentity("myTrigger", "group1") 9 .StartNow() 10 .WithSimpleSchedule(x => x 11 .WithIntervalInSeconds(40) 12 .RepeatForever()) 13 .Build(); 14 15 sched.ScheduleJob(job, trigger); View Code現在定義一個HelloJob 如下所示:
1 public class HelloJob : IJob 2 { 3 public void Execute(IJobExecutionContext context) 4 { 5 Console.WriteLine("HelloJob is executing."); 6 } 7 }
請注意,在這裡給scheduler 一個IJobDetail 實例,它只需要一個job實例就能進行運行。當scheduler執行job的時候,scheduler 會在調用Execute方法之前實例化一個job實例。 能實例化job的前提是job類中需要有個無參的構造函數。還有一個需要注意的是,在job類中定義任何數據字段其實沒有什麼太大的意義,因為在job的運行期間不會保留這些數據字段。
看到這你或許會問,那我如何為一個job提供屬性和配置信息呢?並且這麼能跟蹤保持job的執行狀態呢?要回答這些問題,關鍵要弄懂JobDataMap,它是JobDetail中的一部分。
View CodeJobDataMap中可用於容納任何數量的您希望提供給job實例在執行時(可序列化)的對象。JobDataMap實現了IDictionary接口,並為其添加了一些用於存儲和檢索基本類型的數據的實用方法。
下面是JobDataMap 快速上手代碼,在添加job到scheduler之前先為JobDataMap添加一些數據 :
1 // define the job and tie it to our DumbJob class 2 IJobDetail job = JobBuilder.Create<DumbJob>() 3 .WithIdentity("myJob", "group1") // name "myJob", group "group1" 4 .UsingJobData("jobSays", "Hello World!") 5 .UsingJobData("myFloatValue", 3.141f) 6 .Build();下面是從執行的job中獲取JobDataMap 數據
1 Getting Values from a JobDataMap 2 public class DumbJob : IJob 3 { 4 public void Execute(JobExecutionContext context) 5 { 6 JobKey key = context.JobDetail.Key; 7 8 JobDataMap dataMap = context.JobDetail.JobDataMap; 9 10 string jobSays = dataMap.GetString("jobSays"); 11 float myFloatValue = dataMap.GetFloat("myFloatValue"); 12 13 Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue); 14 } 15 }如果你准備使用一個持久的JobStore (JobStore 將在JobStore 部分進行討論)你應該關注將在JobDataMap放些什麼。因為它會被序列化,所以更容易產生版本問題。在標准的版本中是安全的,除此之外,任何人都可以改變你的實體類。所以不得不關心兼容性會被破壞的情況。
所以你可以把AdoJobStore and JobDataMap 放進map中,僅包含著原始函數與字符串數據,因此消除了序列化的各種問題。
由於Quartz默認JobFactory會再job實例化的時候去實例那些帶有set屬性的,所以當你添加帶有set訪問的屬性的時候會在JobDataMap中創建對應的key將其保存。這樣就不要進行顯示區指示在execute方法方法中進行映射。
Trigger也有其相關的JobDataMap。當你需要多個Trigger進行調度和重復scheduler 這是非常有用的。每個Trigger是獨立的,並且需要你單獨輸入配置信息。
JobDataMap 將JobExecutionContext 作為job執行時候的上下文。它裡面包含了JobDataMap 和Trigger並且覆蓋前面相同的值。
下面是來自JobExecutionContext獲取數據的作業執行過程中合並的JobDataMap的一個簡單的例子:
1 public class DumbJob : IJob 2 { 3 public void Execute(IJobExecutionContext context) 4 { 5 JobKey key = context.JobDetail.Key; 6 7 JobDataMap dataMap = context.MergedJobDataMap; // Note the difference from the previous example 8 9 string jobSays = dataMap.GetString("jobSays"); 10 float myFloatValue = dataMap.GetFloat("myFloatValue"); 11 IList<DateTimeOffset> state = (IList<DateTimeOffset>) dataMap["myStateData"]; 12 state.Add(DateTimeOffset.UtcNow); 13 14 Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue); 15 } 16 }
或者,如果你想依靠的JobFactory“注入”數據映射值到你的類,它可能看起來像這個:
1 public class DumbJob : IJob 2 { 3 public string JobSays { private get; set; } 4 public float FloatValue { private get; set; } 5 6 public void Execute(IJobExecutionContext context) 7 { 8 JobKey key = context.JobDetail.Key; 9 10 JobDataMap dataMap = context.MergedJobDataMap; // Note the difference from the previous example 11 12 IList<DateTimeOffset> state = (IList<DateTimeOffset>) dataMap["myStateData"]; 13 state.Add(DateTimeOffset.UtcNow); 14 15 Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + JobSays + ", and val is: " + FloatValue); 16 } 17 } View Code你會注意到類的整個代碼較長,但在execute()方法的代碼是干淨。人們還可以爭辯說,雖然代碼越長,它實際上花了更少的編碼,如果程序員的IDE用於自動生成的屬性,而不必手工編寫單獨的調用從JobDataMap中檢索值。這是你的選擇。
很多用戶花費時間是困惑究竟是什麼構成了“Job實例”。我們會盡力講清楚這裡,下面有關的工作狀態和並發性的部分。
您可以創建一個job類,並通過創建JobDetails的多個實例的調度中存儲了很多“實例定義” - 每一個都有自己的屬性和JobDataMap - 並且他們都增加到scheduler中
例如,你可以創建一個實現所謂的“SalesReportJob”的IJob接口的類。這個job可能會被編碼到期望發送給它(通過JobDataMap中)來指定銷售報告應根據銷售人員的姓名參數。然後它們可以創建多個定義的job(JobDetails),如“SalesReportForJoe”和“SalesReportForMike”具有“喬joe”和“Mike”在相應JobDataMaps作為輸入到各自的job指定。
當Trigger啟動,將一個JobDetail(實例定義),它會被自動加載,和job類是指通過對調度配置的JobFactory實例化。默認的JobFactory只是調用使用Activator.CreateInstance 調用job類的默認構造函數,然後嘗試在匹配的JobDataMap中的鍵名該類調用setter屬性。您可能希望創建自己的實現的JobFactory來完成的事情,如讓你的應用程序的IoC或者DI容器產生/初始化作業實例。
In “Quartz speak”, 我們指的是每個存儲的JobDetail作為“job定義”或“的JobDetail實例”,我們指的是每一個執行job作為“job實例”或“job定義實例”。通常,如果我們只是用這個詞的“job”,我們指的是一個名為定義或JobDetail等。當我們指的是類實現job接口,我們平時使用的術語“job type”。
現在有一些關於Job的狀態數據(aka JobDataMap)和並發性附加說明。可以天劍組合特性到你的job類上,來影響Quartz’的行為。
DisallowConcurrentExecution添加到Job類,告訴Quartz不執行給定的job定義的多個實例(即是指給定的job類)並發的屬性。注意這裡面的所說,必須小心使用。在上一節的例子中,如果“SalesReportJob”有這個屬性,比只有一個“SalesReportForJoe”的實例可以在給定時間執行的,但它可以與“SalesReportForMike”的一個實例,同時執行。約束是基於一個實例定義(JobDetail等),而不是在工作類的實例。但是,它(quartz的設計),決定對類本身攜帶的屬性,因為它決定對類進行怎樣進行編譯。
PersistJobDataAfterExecution是可以被添加到Job類,告訴quartz更新JobDetail的JobDataMap存儲的副本屬性在execute()方法成功完成後(未拋出異常),使得同樣的job在下一次執行(JobDetail)接收,而不是原先存儲的值的更新的值。像DisallowConcurrentExecution屬性,這適用於作業定義實例,而不是一個作業類的實例,雖然當時決定讓job類攜帶的屬性,因為它往往使對類是如何編碼的差異(如'有狀態'將需要顯式地“理解”的執行方法中的代碼)。
如果使用PersistJobDataAfterExecution屬性,你應該認真考慮也使用DisallowConcurrentExecution屬性,以避免留下什麼數據時,同樣的job(JobDetail)的兩個實例並發執行存儲可能的混淆(競爭條件)
下面是可用於通過JobDetail等對象的job實例來定義的其他屬性的簡單總結:
持久性 - 如果job是不可持久的,它會自動從調度中刪除,一旦不再有與之相關的任何活動的觸發器。換句話說,非持久job具有一個壽命由其觸發的存在的限制。
可恢復性 - 如果作業“要求恢復”,它是在調度的“硬關閉”的時間執行(即它崩潰中運行的過程中,或在機器關閉),然後重新執行當調度程序重新開始。在這種情況下,JobExecutionContext.Recovering屬性將返回真。
最後,我們需要告訴你的IJob.Execute(..)方法的一些細節。你應該從執行方法拋出的唯一類型是JobExecutionException。正因為如此,你通常應該換execute方法的全部內容以'的try-catch“塊。你也應該花一些時間看的JobExecutionException的文檔,你的工作可以用它來提供調度各種指令為你想怎麼異常進行處理。