Quartz.NET教程_Lesson 3: More About Jobs & JobDetails

課程3:詳解作業和作業詳情

As you saw in Lesson 2, jobs are rather easy to implement. There are just a few more things that you need to understand about the nature of jobs, about the Execute(..) method of the IJob interface, and about JobDetails.

就像之前課程2裏面看到的,作業的實現是相當容易的。只不過爲了更深刻的瞭解作業的本質,你還需要了解更多的細節,關於IJob接口的Execute(..)方法還有JobDetails(作業詳情類).

While a job class that you implement has the code that knows how do do the actual work of the particular type of job, Quartz.NET needs to be informed about various attributes that you may wish an instance of that job to have. This is done via the JobDetail class, which was mentioned briefly in the previous section.

當一個你實現的作業類具備了實現某種特定特定功能的代碼,Quartz.NET框架需要知道關於你實現的這個作業類實例的各種屬性。這些工作需要通過JobDetail類來實現,這在之前的部分曾簡單的提過。

JobDetail instances are built using the JobBuilder class. JobBuilder allows you to describe your job’s details using a fluent interface.

JobDetail(作業詳情類)的實例需要藉助JobBuilder類來進行創建。JobBuilder(作業建造者)可以通過連貫接口的形式描述你作業的詳細參數。

Let’s take a moment now to discuss a bit about the ‘nature’ of jobs and the life-cycle of job instances within Quartz.NET. First lets take a look back at some of that snippet of code we saw in Lesson 1:
我們先來簡單的花點兒時間討論一下作業的本質和Quartz.NET框架中作業實例的生命週期。首先,我們看一段課程一里面提到的代碼塊:


Using Quartz.NET
使用Quartz.NET

// define the job and tie it to our HelloJob class
IJobDetail job = JobBuilder.Create<HelloJob>()
    .WithIdentity("myJob", "group1")
    .Build();

// Trigger the job to run now, and then every 40 seconds
ITrigger trigger = TriggerBuilder.Create()
  .WithIdentity("myTrigger", "group1")
  .StartNow()
  .WithSimpleSchedule(x => x
      .WithIntervalInSeconds(40)
      .RepeatForever())
  .Build();

sched.ScheduleJob(job, trigger);
Now consider the job class HelloJob defined as such:
public class HelloJob : IJob
{
    public void Execute(IJobExecutionContext context)
    {
        Console.WriteLine("HelloJob is executing.");
    }
}

Notice that we give the scheduler a IJobDetail instance, and that it refers to the job to be executed by simply providing the job’s class. Each (and every) time the scheduler executes the job, it creates a new instance of the class before calling its Execute(..) method. One of the ramifications of this behavior is the fact that jobs must have a no-arguement constructor. Another ramification is that it does not make sense to have data-fields defined on the job class - as their values would not be preserved between job executions.

請注意我們給調度器提供了一個IJobDetail接口的實例,並且這個實例僅僅通過提供作業類名即表達出了你要執行的作業。每一次調度器在執行作業的時候,調度器都會在調用Execute(..)方法之前創建一個實例。這種行爲造成的其中一種後果就是作業類必須擁有一個無參的構造函數。另一個後果是在作業類中定義數據域是沒有意義的,因爲在每一次執行的過程中,這些值都不會被保留。

You may now be wanting to ask “how can I provide properties/configuration for a Job instance?” and “how can I keep track of a job’s state between executions?” The answer to these questions are the same: the key is the JobDataMap, which is part of the JobDetail object.

你也許現在想問“我怎麼才能給一個作業實例提供屬性/設置參數呢?”或者“在執行的過程中,我怎麼才能保存作業的狀態呢?”對於這些問題的答案都是一樣的:通過JobDataMap實現,這個是JobDetail(作業詳情類)對象的一部分。


JobDataMap
作業數據映射

The JobDataMap can be used to hold any number of (serializable) objects which you wish to have made available to the job instance when it executes. JobDataMap is an implementation of the IDictionary interface, and has some added convenience methods for storing and retrieving data of primitive types.
JobDataMap可以被用來保存任何數量的(可序列化的)對象,這些對象是你希望在執行過程中提供給作業實例的。JobDataMap是一個IDictionary字典接口的實現,並且爲了方便添加了一些用來存儲和檢索原始類型的數據的方法。

Here’s some quick snippets of putting data into the JobDataMap prior to adding the job to the scheduler:
這裏是一些向JobDataMap中添加數據的代碼塊,用來給調度器的作業提供數據。


Setting Values in a JobDataMap
在JobDataMap中設置參數值

// define the job and tie it to our DumbJob class
IJobDetail job = JobBuilder.Create<DumbJob>()
    .WithIdentity("myJob", "group1") // name "myJob", group "group1"
    .UsingJobData("jobSays", "Hello World!")
    .UsingJobData("myFloatValue", 3.141f)
    .Build();
Here’s a quick example of getting data from the JobDataMap during the job’s execution:
Getting Values from a JobDataMap
public class DumbJob : IJob
{
    public void Execute(JobExecutionContext context)
    {
      JobKey key = context.JobDetail.Key;

      JobDataMap dataMap = context.JobDetail.JobDataMap;

      string jobSays = dataMap.GetString("jobSays");
      float myFloatValue = dataMap.GetFloat("myFloatValue");

      Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
}

If you use a persistent JobStore (discussed in the JobStore section of this tutorial) you should use some care in deciding what you place in the JobDataMap, because the object in it will be serialized, and they therefore become prone to class-versioning problems. Obviously standard .NET types should be very safe, but beyond that, any time someone changes the definition of a class for which you have serialized instances, care has to be taken not to break compatibility.

如果你用了一致的JobStore作業存儲類(在JobStore一章中會詳細討論)你需要在JobDataMap十分小心,因爲這些對象會被序列化,並且有可能被誤認爲是類的版本問題。很明顯標準的.NET類型應該是很安全的,但是更進一步看,在任何時候當我們想序列化一個實例的時候,必須要注意不要破壞其兼容性。

Optionally, you can put AdoJobStore and JobDataMap into a mode where only primitives and strings can be stored in the map, thus eliminating any possibility of later serialization problems.

另一種方式,你可以將AdoJobStore和JobDataMap組合成一種模式,僅僅允許保存基本數據類型和字符串,這樣的話,可以減少在後面使用的時候出現序列化的問題。

If you add properties with set accessor to your job class that correspond to the names of keys in the JobDataMap, then Quartz’s default JobFactory implementation will automatically call those setters when the job is instantiated, thus preventing the need to explicitly get the values out of the map within your execute method.

如果你給你的作業類定義了set屬性方法,並且和JobDataMap中存儲的數據的鍵一致,則 Quartz框架的默認作業工廠方法會在作業實例化的時候自動的調用這些set方法,這樣可以避免你在執行作業方法的時候,還明確的定義出需要取出的存在其中的數據的值。

Triggers can also have JobDataMaps associated with them. This can be useful in the case where you have a Job that is stored in the scheduler for regular/repeated use by multiple Triggers, yet with each independent triggering, you want to supply the Job with different data inputs.

觸發器同樣有與其關聯的 JobDataMaps容器。當你在一個調度器中有一個作業,但是會被多個觸發器觸發執行,同時,你又希望能夠提供給作業不同的參數的時候,非常有用。

The JobDataMap that is found on the JobExecutionContext during Job execution serves as a convenience. It is a merge of the JobDataMap found on the JobDetail and the one found on the Trigger, with the values in the latter overriding any same-named values in the former.

JobExecutionContext中定義的JobDataMap只是爲了方便。這個是定義在JobDetail和Trigger中JobDataMap的一個分支,在存儲項目命名的數據時,數據的值會被最新的數據覆蓋。

Here’s a quick example of getting data from the JobExecutionContext’s merged JobDataMap during the job’s execution:

下面是一個在作業執行過程中,從JobExecutionContext的分支JobDataMap中獲取數的一個案例:


public class DumbJob : IJob
{
    public void Execute(IJobExecutionContext context)
    {
        JobKey key = context.JobDetail.Key;

        JobDataMap dataMap = context.MergedJobDataMap;  // Note the difference from the previous example

        string jobSays = dataMap.GetString("jobSays");
        float myFloatValue = dataMap.GetFloat("myFloatValue");
        IList<DateTimeOffset> state = (IList<DateTimeOffset>) dataMap["myStateData"];
        state.Add(DateTimeOffset.UtcNow);

        Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
    }
}
Or if you wish to rely on the JobFactory “injecting” the data map values onto your class, it might look like this instead:
public class DumbJob : IJob
{
    public string JobSays { private get; set; }
    public float FloatValue { private get; set; }

    public void Execute(IJobExecutionContext context)
    {
        JobKey key = context.JobDetail.Key;

        JobDataMap dataMap = context.MergedJobDataMap;  // Note the difference from the previous example

        IList<DateTimeOffset> state = (IList<DateTimeOffset>) dataMap["myStateData"];
        state.Add(DateTimeOffset.UtcNow);

        Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + JobSays + ", and val is: " + FloatValue);
    }
}

You’ll notice that the overall code of the class is longer, but the code in the Execute() method is cleaner. One could also argue that although the code is longer, that it actually took less coding, if the programmer’s IDE was used to auto-generate the properties, rather than having to hand-code the individual calls to retrieve the values from the JobDataMap. The choice is yours.

你會發現這個類的整體代碼都要顯得更多,但是Execute()方法卻更爲清晰。你也許會說,儘管代碼更多,實際上當程序員使用IDE進行自動提示模式進行開發時,相比單獨的從JobDataMap中獲取數據值,開發的工作量反而是少的。究竟怎麼選,這個是你自己的事情。


Job “Instances”
作業實例

Many users spend time being confused about what exactly constitutes a “job instance”. We’ll try to clear that up here and in the section below about job state and concurrency.
很多用戶在構建作業實例的時候產生了疑惑。在下面一部分,我們會盡量清晰的闡述作業的狀態和一致性。

You can create a single job class, and store many ‘instance definitions’ of it within the scheduler by creating multiple instances of JobDetails - each with its own set of properties and JobDataMap - and adding them all to the scheduler.
你可以創建一個單獨的作業類,同時可以通過創建多個JobDetails的實例,在調度器中存儲多個實例的定義。其中的每一個作業詳情類都具備獨立的屬性和JobDataMap數據,同時

For example, you can create a class that implements the IJob interface called “SalesReportJob”. The job might be coded to expect parameters sent to it (via the JobDataMap) to specify the name of the sales person that the sales report should be based on. They may then create multiple definitions (JobDetails) of the job, such as “SalesReportForJoe” and “SalesReportForMike” which have “joe” and “mike” specified in the corresponding JobDataMaps as input to the respective jobs.

比如,你可以創建一個“SalesReportJob”(銷售報表作業)類,用來實現IJob接口。這個作業也許是需要傳遞參數,基於不同的銷售人員產生不同的銷售報表。他們也許會定義不同的JobDetails作業詳情,比如“SalesReportForJoe”(喬治的銷售報告)和“SalesReportForMike”(麥克的銷售報告),在作業詳情中,通過將“joe” 和“mike”存在JobDataMaps中來對相應的作業進行定義。

When a trigger fires, the JobDetail (instance definition) it is associated to is loaded, and the job class it refers to is instantiated via the JobFactory configured on the Scheduler. The default JobFactory simply calls the default constructor of the job class using Activator.CreateInstance, then attempts to call setter properties on the class that match the names of keys within the JobDataMap. You may want to create your own implementation of JobFactory to accomplish things such as having your application’s IoC or DI container produce/initialize the job instance.

當觸發器開始執行,其相關的JobDetail作業詳情(實例定義)就會加載,同時相關的作業類會通過調度器設置的JobFactory工廠類進行實例化。默認的JobFactory工廠方法會通過Activator.CreateInstance方法喚起作業類的默認構造函數,然後嘗試吊起JobDataMap中鍵相匹配的setter屬性方法。你也許想創建你自己的JobFactory實現來完成一些事情,比如在作業實例中產生或者初始化你個人應用的IoC(控制反轉)/DI(依賴注入)容器。

In “Quartz speak”, we refer to each stored JobDetail as a “job definition” or “JobDetail instance”, and we refer to a each executing job as a “job instance” or “instance of a job definition”. Usually if we just use the word “job” we are referring to a named definition, or JobDetail. When we are referring to the class implementing the job interface, we usually use the term “job type”.
通過Quartz式的說法,我們將每個存儲的JobDetail叫做“作業定義”或者“作業詳情定義”,而且我們將每一個執行的作業稱作“作業實例”或者“作業定義的實例”。通常我們如果使用“作業”這個詞,我們通常在說一個被命名好的定義或者作業詳情。當我們想要描述被實現的作業接口類的時候,我們通常會說“作業類型”。


Job State and Concurrency
作業狀態和一致性

Now, some additional notes about a job’s state data (aka JobDataMap) and concurrency. There are a couple attributes that can be added to your Job class that affect Quartz’s behaviour with respect to these aspects.

現在關於作業的狀態數據和一致性還有一些需要額外注意的問題。有一些屬性,可以被添加到你的作業類並影響到Quartz框架的相對應的行爲。

DisallowConcurrentExecution is an attribute that can be added to the Job class that tells Quartz not to execute multiple instances of a given job definition (that refers to the given job class) concurrently. Notice the wording there, as it was chosen very carefully. In the example from the previous section, if “SalesReportJob” has this attribute, than only one instance of “SalesReportForJoe” can execute at a given time, but it can execute concurrently with an instance of “SalesReportForMike”. The constraint is based upon an instance definition (JobDetail), not on instances of the job class. However, it was decided (during the design of Quartz) to have the attribute carried on the class itself, because it does often make a difference to how the class is coded.

DisallowConcurrentExecution這個特性可以被添加到作業類當中,並告訴Quartz框架不要在同時執行多個給定的作業實例。注意這裏的表述方式,這些詞是被仔細斟酌過的。在之前一部分的例子中,如果“SalesReportJob”作業有這個特性,則在給定時間只能執行一個“SalesReportForJoe”的實例,但是同時卻可以執行一個“SalesReportForMike”的實例。這個限制是基於實例定義的(作業詳情),並不是基於作業類的。無論如何,這個在框架的設計過程中,這個屬性是伴隨着類本身的,因爲這個特性通常關係着類中的代碼如何來設計。

PersistJobDataAfterExecution is an attribute that can be added to the Job class that tells Quartz to update the stored copy of the JobDetail’s JobDataMap after the Execute() method completes successfully (without throwing an exception), such that the next execution of the same job (JobDetail) receives the updated values rather than the originally stored values. Like the DisallowConcurrentExecution attribute, this applies to a job definition instance, not a job class instance, though it was decided to have the job class carry the attribute because it does often make a difference to how the class is coded (e.g. the ‘statefulness’ will need to be explicitly ‘understood’ by the code within the execute method).

PersistJobDataAfterExecution這個特性也可以被添加到作業類當中,並告訴Quartz框架在成功執行完Execute()方法之後去更新一個作業詳情中JobDataMap數據的副本,這樣的話,在下一次的執行過程中,同一個作業(作業詳情)就可以獲取到更新後的數據,而不是之前的原始數據。正如DisallowConcurrentExecution一樣,這個特性被應用於一個作業定義實例,而不是一個作業類實例。儘管作業類在架構設計的過程中就被決定了要帶這個特性,因爲這個通常關係到類中的代碼如何被設計(比如,“有狀態性”會在執行方法的時候被代碼明確的“理解”)。

If you use the PersistJobDataAfterExecution attribute, you should strongly consider also using the DisallowConcurrentExecution attribute, in order to avoid possible confusion (race conditions) of what data was left stored when two instances of the same job (JobDetail) executed concurrently.

如果你使用PersistJobDataAfterExecution特性,你應該強烈的考慮同時使用DisallowConcurrentExecution特性,爲了避免在兩個同樣作業(作業詳情)的實例在在同時執行的時候,產生可能的概念混淆(小概率)搞不清楚實例中到底存了什麼數據。


Other Attributes Of Jobs
作業的一些其他特性

Here’s a quick summary of the other properties which can be defined for a job instance via the JobDetail object:

這裏有一個簡單的總結,通過作業詳情對象來定義作業實例還包含那些其他的屬性:

Durability - if a job is non-durable, it is automatically deleted from the scheduler once there are no longer any active triggers associated with it. In other words, non-durable jobs have a life span bounded by the existence of its triggers.

持久性 - 如果一個作業是費持久的,則在調度器當中不存在相關的觸發器時,會被自動清除。換句話說,非持久的作業的生命週期是由其觸發器的存在決定的。

RequestsRecovery - if a job “requests recovery”, and it is executing during the time of a ‘hard shutdown’ of the scheduler (i.e. the process it is running within crashes, or the machine is shut off), then it is re-executed when the scheduler is started again. In this case, the JobExecutionContext.Recovering property will return true.

請求恢復 - 如果一個作業進行“請求恢復”,並在調度器出現崩潰的時候執行,則當調度器被重新啓動之後會被再次執行。在這種情況下,JobExecutionContext.Recovering屬性會返回真值。


JobExecutionException
作業執行異常

Finally, we need to inform you of a few details of the IJob.Execute(..) method. The only type of exception that you should throw from the execute method is the JobExecutionException. Because of this, you should generally wrap the entire contents of the execute method with a ‘try-catch’ block. You should also spend some time looking at the documentation for the JobExecutionException, as your job can use it to provide the scheduler various directives as to how you want the exception to be handled.


希望我的文字能給你帶來幫助:

碼字不易,與君共勉!

發佈了31 篇原創文章 · 獲贊 39 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章