作業流程是在排程器的統一排程下完成的,它可以排程多個作業,觸發器提供作業執行的條件(每天 8:00 am),觸發器與作業關聯,它們是 1:N 的關系,1個觸發器可以關聯1個或多個作業。
附帶的作業
我們知道要實作自己的作業功能隻要繼承 IJob 接口并實作 Execute(JobExecutionContext context) 方法,再把它添加到排程器,排程器會調用執行Execute(JobExecutionContext context) 方法。調用期間,排程器會跟蹤作業和它們的執行次數。Quartz.NET 預設提供了 FileScanJob 監視某個檔案是否被修改,NativeJob 執行指定程式,NoOpJob 空操作用來給系統調用 ITriggerListener 、IJobListener ,以及 SendMailJob 郵件發送作業
它有一個存儲作業執行時資料的重要屬性:MergedJobDataMap,它是 JobDataMap 類型,也可以使用 JobExecutionContext.JobDetail.JobDataMap 來獲得 JobDataMap 對象的引用。作為傳遞參數的容器,JobDataMap 間接繼承了 DirtyFlagMap,DirtyFlagMap 内嵌了 Hashtable 容器,它有一組資料讀寫方法。可以使用 Visual Studio 的類圖來檢視。
當實作一個真實的作業類時,一些屬性是需要告知Quartz的,也是您希望該作業需要有的。這就需要通過JobDetail類,這個類在前面我們已經簡單介紹過。
下面我們看一下作業的本質,以及生命周期。還是看看前面的例子:
Lesson1
// construct a scheduler factory
ISchedulerFactory schedFact = new StdSchedulerFactory();
// get a scheduler
IScheduler sched = schedFact.GetScheduler();
sched.Start();
// construct job info
JobDetail jobDetail = new JobDetail("myJob", null, typeof(DumbJob));
// fire every hour
Trigger trigger = TriggerUtils.MakeHourlyTrigger();
// start on the next even hour
trigger.StartTime = TriggerUtils.GetEvenHourDate(DateTime.UtcNow);
trigger.Name = "myTrigger";
sched.ScheduleJob(jobDetail, trigger);
public class DumbJob : IJob
{
public DumbJob() { }
publicvoid Execute(JobExecutionContext context)
{
Console.WriteLine("DumbJob is executing.");
}
}
如上例所示,向scheduler提供JobDetail執行個體,就能指向作業執行。scheduler每次執行(Execute方法)作業前,都新生成一個作業執行個體。由于是沒有參數的構造器,每次作業執行,資料都沒清一次。
JobDataMap
在作業執行個體執行的時候,JobDataMap可以用來儲存任何數量對象(可序列化)的。JobDataMap繼承自IDictionary接口,添加一些便捷的方法,以便存儲和獲得原型資料。
以下是添加到scheduler之前,向JobDataMap存放資料的代碼片段:
jobDetail.JobDataMap[
"jobSays"
] =
"Hello World!"
;
jobDetail.JobDataMap[
"myFloatValue"
] = 3.141f;
jobDetail.JobDataMap[
"myStateData"
] =
new
ArrayList();
以下是在作業執行時,取資料的代碼片段:
public
class
DumbJob : IJob
{
public
void
Execute(JobExecutionContext context)
{
string
instName = context.JobDetail.Name;
string
instGroup = context.JobDetail.Group;
JobDataMap dataMap = context.JobDetail.JobDataMap;
string
jobSays = dataMap.GetString(
"jobSays"
);
float
myFloatValue = dataMap.GetFloat(
"myFloatValue"
);
ArrayList state = (ArrayList) dataMap[
"myStateData"
];
state.Add(DateTime.UtcNow);
Console.WriteLine(
"Instance {0} of DumbJob says: {1}"
, instName, jobSays);
}
}
如果需要用JobStore來做持久化的話,就需要小心存在JobDataMap的是什麼了。因為存放的對象是要被序列化的,那麼就很容易出現對象的版本問題。顯然,.Net的标準資料類型是安全的,除此之外,序列化的對象執行個體,任何變化,都會導緻版本相容問題。或者幹脆隻允許原型資料類型和字元串能儲存,這樣能避免以後序列化版本沖突問題。
在作業執行時,JobDataMap可以從JobExecutionContext中獲得。這時候它是JobDetail和Trigger中的JobDataMap合并,是後來同名對象的覆寫。
下面是在作業執行時,從JobExecutionContext中獲得JobDataMap的代碼片段:
public class DumbJob : IJob
{
public void Execute(JobExecutionContext context)
{
string instName = context.JobDetail.Name;
string instGroup = context.JobDetail.Group;
// Note the difference from the previous example
JobDataMap dataMap = context.MergedJobDataMap;
string jobSays = dataMap.GetString("jobSays");
float myFloatValue = dataMap.GetFloat("myFloatValue");
ArrayList state = (ArrayList) dataMap.Get("myStateData");
state.Add(DateTime.UtcNow);
Console.WriteLine("Instance {0} of DumbJob says: {1}", instName, jobSays);
}
}
有狀态與無狀态的作業
以上我們的作業執行個體都是從 IJob 繼承,Quartz.NET 裡還有 IStatefulJob 、IInterruptableJob,它的聲明方式為:
public interface IStatefulJob : IJob
{
}
public interface IInterruptableJob : IJob
{
void Interrupt();
}
IInterruptableJob 接口提供了一個中斷方法,但是 IStatefulJob 沒有自己的方法。從 Quartz.NET 官方了解到:
一個 Job 執行個體可以被定義為“有狀态的”或者“無狀态的”。在執行無狀态的任務過程中任何對 JobDataMap 所作的更改都将丢失。有狀态的任務恰好相反,它在任務的每次執行之後重新存儲 JobDataMap 。有狀态任務的一個缺點就是它不能并發執行。也就是說,如果任務有狀态,那麼當觸發器試圖觸發它,觸發器就會被阻塞直到前面的執行完成。想使任務有狀态,它就要實作 IStatefulJob 接口而不是實作IJob接口。
作業執行個體,可以被指定為“statefull”,或者“non-statefull”。Non-statefull作業隻在JobDataMap添加到scheduler時有資料。就是意味着,在下一次作業執行時,任何作業狀态資料(JobDataMap)的修改丢将丢失或看不見。正如您猜測的那樣,statefull作業正好相反,作業的每次執行,JobDataMap都會重新儲存,這給statefull作業的負作用,就是不能并發執行。也就是說,如果一個statefull作業,觸發器已經觸發正在執行了,下個觸發器将會被阻塞,直到前一個執行完成。
辨別一個statefull作業,隻要繼承IStatefullJob接口,而不是IJob接口。
Job 'Instances'
這個需要最後明确的是,您可以建立一個作業類,通過建立多個JobDetail執行個體,存儲在scheduler中,每個執行個體都有自己的屬性集及JobDataMap。
當與作業關聯的觸發器觸發時,作業通過JobFactory執行個體化。預設的JobFactory調用Activator.CreateInstance。
Other Attributes Of Jobs
這裡列一下JobDetail對象的其他屬性:
- Durable - 一個non-durable作業, 當沒有與之關聯的active trigger時,會自動從scheduler中删除的。
- Volatile - 一個volatile作業, 在re-starts scheduler之間是不保留的。
- RequestsRecovery - 一個"requests recovery"作業,在scheduler“hard shutdown”的情況下,或者機器斷電的時候,當scheduler重新開機時,作業重新執行。JobExecutionContext.isRecovering()傳回true。
- JobListeners - 作業可以沒有或多個JobListeners。作業執行時,listeners會被提醒。更多讨論後面專門讨論。
The Job.Execute(..) Method
為什麼作業和觸發分開
很多作業排程程式,作業和觸發沒有明顯的分開界限。有些作業定義為一個作業識别号和相應的執行時間。還有些更像Quartz.NET作業和觸發的聚合。當開發Quartz的時候,Quartz team決定,把排程和所要執行的動作分開(trigger)。據我們所知,這将帶來很多便捷。例如:
- 作業可以獨立trigger存儲在scheduler中,多個trigger關聯同一個作業。
- 松耦合可以重新排程已經過期作業,而不需要重新定義。也可以修改替換觸發器而不需要重新定義與之相關的作業。