Quartz.NET笔记(三) More About Jobs & JobDetails
如你所见,Job相当容易实现。这里只是介绍有关Jobs本质, IJob接口的Execute(..)方法以及JobDetails中需要理解的内容。
在所实现的类成为真正的“Job”时,期望任务所具有的各种属性需要通知给Quartz。通过JobDetail类可以完成这个工作,这个类在前面的章节中曾简短提及过。现在,我们花一些时间来讨论Quartz中Jobs的本质和Job实例的生命周期。首先让我们回顾一下第一课中所看到的代码片断:
Using 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);
现在考虑如下定义的 HelloJob类:
1 public class HelloJob : IJob 2 { 3 public void Execute(IJobExecutionContext context) 4 { 5 Console.WriteLine("HelloJob is executing."); 6 } 7 }
注意,我们给scheduler传入了一个JobDetail实例,而且这个JobDetail实例只是简单提供了类名来引用被执行的Job。每次scheduler执行这个任务时,它就创建这个类的新实例,然后调用该实例的Execute(..)方法。对这种行为的一个推论就是Job类必须有一个无参数的构造函数。另外一个推论就是它使得Job类中定义的成员数据失去意义,因为这些成员数据值在每次执行的时候被“清空”了。
你可能要问,如何才能为每个Job实例提供属性和配置呢?而且,在执行中如何跟踪Job的状态呢?这些问题的答案是相同的:关键就是JobDataMap,这是JobDetail对象的一部分。
JobDataMap
JobDataMap被用来保存一系列的(序列化的)对象,这些对象在Job执行时可以得到。JobDataMap是IDictionary接口的一个实现,而且还增加了一些存储和读取主类型数据的便捷方法。
下面是将Job加入到scheduler前使用的一些向JobDataMap加入数据的方法。
Setting Values in a 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 获取数据的代码:
Getting Values from a JobDataMap:
1 public class DumbJob : IJob 2 { 3 public void Execute(JobExecutionContext context) 4 { 5 JobKey key = context.JobDetail.Key; 6 7 JobDataMap dataMap = context.JobDetail.JobDataMap;//注意同下面例子的差别 8 9 string jobSays = dataMap.GetString("jobSays"); 10 float myFloatValue = dataMap.GetFloat("myFloatValue"); 11 12 Console.Error.WriteLine("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue); 13 } 14 }
如果使用一个持久的JobStore(在本指南的JobStore章节中讨论),那么必须注意存放在JobDataMap中的内容。因为放入JobDataMap中的内容将被序列化,而且容易出现类型转换问题。很明显,标准.NET类型将是非常安全的,但除此之外的类型,任何时候,只要有人改变了你要序列化其实例的类的定义,就要注意是否打破了程序的兼容性。另外,你可以对JobStore和JobDataMap采用一种使用模式:就是只把主类型和String类型存放在Map中,这样就可以减少后面序列化的问题。
有状态和无状态任务
Triggers也可以有JobDataMaps与之相关联。当scheduler中的Job被多个有规律或者重复触发的Triggers所使用时非常有用。对于每次独立的触发,你可为Job提供不同的输入数据。
从Job执行时的JobExecutionContext中取得JobDataMap是惯用手段,它融合了从JobDetail和从Trigger中获的JobDataMap,当有相同名字的键时,它用后者的值覆盖前者值。
Here's a quick example of getting data from the JobExecutionContext's merged JobDataMap during the job's execution:
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 }
Or if you wish to rely on the JobFactory "injecting" the data map values onto your class, it might look like this instead:
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 }
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.
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.
很多用户在准确地构建 Job实例时花时间并变得困惑,我们试图在本节和下面的章节解释清楚job的状态和并发性。
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.
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.
当触发器被触发的时候,通过Scheduler中配置的JobFactory来实例化与之关联的Job类。缺省的JobFactory只是简单地对Job类调用GetScheduler ()方法。创建自己JobFactory可以利用应用中诸如Ioc或者DI容器所产生或者初始化的Job实例。
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 speak",我们将每个存储的 JobDetail 称为"作业定义"或"JobDetail 实例",我们指每个执行中的工作作为"作业实例"或"作业定义实例"。通常如果我们只是使用"工作"一词我们指一个命名的定义或 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.
现在,一些附加的说明关于工作状态数据 (aka JobDataMap) 和并发性。有几个属性可以添加到您的工作类影响石英的行为在这些方面。
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"的一个实例。约束基于实例的定义(JobDetail) 而不是基于工作类(job class)的实例。然而,它被决定 (Quartz的设计过程中) 具有属性进行类本身,因为它确实时常令到类如何编码的差异。
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 theDisallowConcurrentExecution 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).
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 属性,为了避免 同一个作业(JobDetail)的两个实例并发地执行时数据被存储的混淆 (争用条件) 的可能。(翻译的有点勉强)
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.
这里简短地总结一下通过JobDetail对象可以定义Job的其它属性。
• Durability(持久性)-如果一个Job是不持久的, 一旦没有触发器与之关联,它就会被从scheduler 中自动删除。
• Volatility(无常性)-如果一个Job是无常的,在重新启动Quartz i scheduler 时它不能被保持。
• RequestsRecovery(请求恢复能力) -如果一个Job具备“请求恢复”能力,当它在执行时遇到scheduler “硬性的关闭”(例如:执行的过程崩溃,或者计算机被关机),那么当scheduler重新启动时,这个任务会被重新执行。这种情况下,JobExecutionContext.Recovering 属性将是true。
• JobListeners(任务监听器) -一个Job如果有0个或者多个JobListeners监听器与之相关联,当这个Job执行时,监听器被会被通知。更多有关JobListeners的讨论见TriggerListeners & JobListeners章节。
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.
最后,需要告诉你一些关于Job.Execute(..)方法的细节。在Execute方法被执行时,仅允许抛出一个JobExecutionException类型异常。因此需要将整个要执行的内容包括在一个'try-catch'块中。应花费一些时间仔细阅读JobExecutionException文档,因为Job能够使用它向scheduler提供各种指示,你也可以知道怎么处理异常。