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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章