这是翻译的官方教程,英语水平好的可以直接看官方原文:https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/using-quartz.html
虽然实现的工作类具有知道特定类型工作的实际工作方式的代码,但Quartz.NET需要了解该工作实例具有的各种属性。这是通过JobDetail类完成的,该类在前面的章节中简要提及。
JobDetail实例是使用JobBuilder类构建的。JobBuilder允许您使用流接口(fluent interface)描述您的工作细节。
现在让我们花点时间来讨论一下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);
现在考虑作业类HelloJob 定义如下:
public class HelloJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
await Console.Out.WriteLineAsync("HelloJob is executing.");
}
}
请注意,我们给调度程序一个IJobDetail实例,它指的是通过简单提供作业的类来执行的作业。调度程序每次执行作业时,都会在调用其Execute(…)方法之前创建该类的新实例。这种行为的一个结果是,作业必须有一个无争议的构造函数。另一个结果是,在作业类上定义数据字段没有意义 - 因为它们的值在作业执行之间不会保留。
您现在可能想问“如何为Job实例提供属性/配置?”以及“如何在执行之间跟踪工作状态?”这些问题的答案是相同的:关键是JobDataMap ,它是JobDetail对象的一部分。
JobDataMap
JobDataMap可以用来保存任何数量的(可序列化)对象,这些对象在作业实例执行时可以提供给作业实例。JobDataMap是IDictionary接口的一个实现,并且有一些额外的便利方法来存储和检索原始类型的数据。
以下是在将作业添加到调度程序之前将数据放入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();
以下是在作业执行期间从JobDataMap获取数据的一个简单示例:
从JobDataMap获取值
public class DumbJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
JobKey key = context.JobDetail.Key;
JobDataMap dataMap = context.JobDetail.JobDataMap;
string jobSays = dataMap.GetString("jobSays");
float myFloatValue = dataMap.GetFloat("myFloatValue");
await Console.Error.WriteLineAsync("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
}
}
如果您使用持久性JobStore(在本教程的JobStore部分中进行了讨论),您应该谨慎决定放置在JobDataMap中的内容,因为其中的对象将被序列化,因此它们容易出现class-versioning问题。很明显,标准的.NET类型应该是非常安全的,但除此之外,任何时候有人改变你已经序列化实例的类的定义时,都必须注意不要打破兼容性。
或者,您可以将AdoJobStore和JobDataMap置于只有原语和字符串可以存储在地图中的模式,从而消除以后序列化问题的可能性。
如果您将job访问器的属性添加到作业类中,并与JobDataMap中的键的名称相对应,那么当作业实例化时,Quartz的默认JobFactory实现将自动调用这些设置器,从而避免需要显式地将值在执行方法内映射。
触发器也可以有与之关联的JobDataMaps。如果您有一个存储在调度程序中的作业,以供多个触发器定期/重复使用,但每次独立触发,您都希望为作业提供不同的数据输入,则此功能非常有用。
作业执行期间在JobExecutionContext上找到的JobDataMaps是很便利的。它是在JobDetail上找到的JobDataMap和在Trigger上找到的JobDataMap的合并,后者中的值覆盖前者中的任何相同名称的值。
以下是在作业执行期间从JobExecutionContext的MergedJobDataMap获取数据的简单示例:
public class DumbJob : IJob
{
public async Task 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);
await Console.Error.WriteLineAsync("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
}
}
或者,如果您希望依赖JobFactory将数据映射值“注入”到您的类中,它可能会看起来像这样:
public class DumbJob : IJob
{
//属性值必须和
public string JobSays { private get; set; }
public float FloatValue { private get; set; }
public async Task 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);
await Console.Error.WriteLineAsync("Instance " + key + " of DumbJob says: " + JobSays + ", and val is: " + FloatValue);
}
}
您会注意到该类的整体代码更长,但Execute()方法中的代码更清晰。人们也可以争辩说,虽然代码更长,但如果使用程序员的IDE来自动生成属性,而不是手动编码单个调用以从JobDataMap中检索值,那么实际上需要更少的代码。这是你的选择。
作业“实例”
许多用户花时间对什么构成“作业实例”感到困惑。我们将尝试在这里和下面的部分中澄清关于作业状态和并发性的内容。
您可以创建一个单一的作业类,把它的许多“实例定义”存储在创建了JobDetails的多个实例的调度程序。
- 每个都有自己的一组属性和JobDataMap - 并将它们全部添加到调度程序中。
例如,您可以创建一个实现IJob接口的“SalesReportJob”类。作业可能把发送给它的参数(通过JobDataMap)用来指定销售人员的名称。然后,他们可以创建作业的多个定义(JobDetails),例如“SalesReportForJoe”和“SalesReportForMike”,它们对应作业的输入参数有JobDataMaps中指定的“joe”和“mike”。
当触发器触发时,它所关联的JobDetail(实例定义)将被加载,并且它引用的作业类将通过Scheduler上配置的JobFactory实例化。默认的JobFactory只是使用Activator.CreateInstance调用作业类的默认构造函数,然后尝试调用该类的匹配JobDataMap中的键的名称的setter属性。您可能希望创建自己的JobFactory实现来完成诸如让应用程序的IoC或DI容器生成/初始化作业实例等事情。
在“Quartz speak”中,我们将每个存储的JobDetail称为“作业定义”或“JobDetail实例”,并将每个正在执行的作业称为“作业实例”或“作业定义实例”。通常,如果我们只使用“job”这个词,我们就是指一个命名定义,或者JobDetail。当我们指的是实现job interface的类时,我们通常使用术语“job type”。
作业状态和并发性
现在,关于作业状态数据(又名JobDataMap)和并发性的附加说明。有几个属性可以添加到您的Job类中,从而影响Quartz在这些方面的行为。
DisallowConcurrentExecution是一个可以添加到Job类中的属性,告诉Quartz不要同时执行给定作业定义(指给定作业类)的多个实例。注意这里的措词,因为它是非常仔细地选择的。在上一节的示例中,如果“SalesReportJob”具有此属性,则在给定时间只能执行一个“SalesReportForJoe”实例, 但它可与“SalesReportForMike”实例同时执行。约束基于实例定义 (JobDetail), 而不是作业类的实例。然而,因为它通常会影响类的编码方式,它可以决定(在Quartz的设计中)是否将属性放在类本身上。
PersistJobDataAfterExecution是一个可以添加到Job类中的属性,它告诉Quartz在Execute()方法成功完成(不抛出异常)之后更新JobDetail的JobDataMap的存储副本,以便下次执行同一作业(JobDetail)时接收到的数据是更新的值而不是最初存储的值。与DisallowConcurrentExecution属性类似,这适用于作业定义实例,而不是作业类实例,它能够决定让作业类携带的属性,原因是它会影响类的编码方式(例如“状态性”将需要被执行方法内的代码明确’理解’)。
如果使用PersistJobDataAfterExecution属性,则应该强烈考虑使用DisallowConcurrentExecution属性,以避免在同时执行同一作业(JobDetail)的两个实例时可能会混淆(竞争条件)哪些数据存储在哪里。
其他job属性
以下是可以通过JobDetail对象为作业实例定义的其他属性的简要摘要:
- Durability - 如果作业不耐用,则一旦不再有任何与其关联的活动触发器,它就会自动从调度程序中删除。换句话说,非耐久性工作的寿命受触发器存在的限制。
- RequestsRecovery - 如果一个作业“请求恢复”,并且它在调度器的“硬关闭”期间执行(即它在崩溃中运行的进程或者机器被关闭),那么当调度程序再次启动时它被重新执行。在这种情况下,JobExecutionContext.Recovering属性将返回true。
JobExecutionException
最后,我们需要通知您关于IJob.Execute(…)方法的一些细节。您应该从execute方法抛出的唯一异常类型是JobExecutionException。因此,通常应该用“try-catch”块来包装execute方法的全部内容。您还应该花一些时间查看JobExecutionException的文档,因为如何处理异常决定你使用的job可以为任务提供哪些指令。