jobs 很容易实现. 关于它,你还需要了解 Execute(..) 和 JobDetails.
当你的 job 类知道需要做什么操作后, Quartz.NET 就需要知道执行该 job 实例的一些属性(attribute). 这些属性可以通过 jobdetail 来定义.
JobDetail 实例用 JobBuilder 类来构建. JobBuilder 允许你通过 fluent interface 来定义一个 JobDetail 实例.
现在来瞅瞅 Quartz.NET 中 Job 的生命周期. 首先,看课程一中的这段代码:
Using 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 void Execute(IJobExecutionContext context)
{
Console.WriteLine("HelloJob is executing.");
}
}
我们给 scheduler 传递一个 IJobDetail 实例, 这个实例通过 Job 类名指向它相应的 job 实例. 每当 scheduler 执行 job, 在执行 Execute() 前,它都构造一个新的 job 实例. 注意 jobs 必须有一个无参构造函数. 另外在 job 类中定义字段是没有意义的 - 因为他们的值在每次执行过程中无法被保存.
那么我们怎么给一个 job 实例提供属性和配置呢? 另外怎么维护 job 实例的执行状态? 答案是同一个: JobDataMap, 它是 JobDetail 对象的一部分.
JobDataMap
JobDataMap 用来存储任何可序列号对象. 然后就可以在 job 实例执行的时候用这些对象. JobDataMap 实现了 IDictionary 接口, 且提供了些便捷的方法来存储或者获取基础类型数据.
以下代码显示怎样在把 job 对象放到 scheduler 之前,放数据到 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();
然后是在 job 执行时,从 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);
}
}
如果你要用 JobStore,你应该先考虑下要在 JobDataMap 放哪些数据, 因为在 JobStore 里面的数据会被序列化, 这时候很容易导致一些 class-versioning(WTF) 问题. 我们都希望 .NET 是类型安全的, 问题在于任何人改了你的序列化类的类型定义, 都需要额外小心不会影响到现有功能.
或者, 你可以把 AdoJobStore 和 JobDataMap 放到一个模式里面,这样只有基础类型和字符串可以存在 map 中, 这就可以消除以后的序列化问题.
如果 JobDataMap 指向一个 job, 且你的 job 类中有可写属性, 那么 Quartz 就会在 job 初始化时用默认的 JobFactory 来自动给 job 的这些属性赋值, 这样就不需要在执行时从 JobDataMap 中显示的取出一个值来给 job 执行.
Triggers 也可以有与之关联的 JobDataMaps. 如果你的 scheduler 中有个 job 需要被多个不同的 Triggers 重复使用, 这个特性就很有帮助了, 不过对于每次不同的 Trigger 触发动作, 你需要给 job 提供不同的输入.
存储在 JobExecutionContext 当中的 JobDataMap , 在 Job 执行期间提供数据服务. 它是 JobDetail 的JobDataMap 和 Trigger 的 JobDetail 的集合, 它会覆盖前两者里面同一个键的键值.
看个关于在 job 执行时从 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);
}
}
或者可以用 JobFactory “注入” 键值到你的 job 类中, 如下代码所示:
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);
}
}
你会看到代码量多了, 但是 Execute 方法更简洁了.
Job "Instances"
那么到底什么是 job 实例.
你可以定义一个简单的 job 类, 然后在 scheduler 中保存许多该类的实例和与之关联的 JobDetails 实例, 每个都有它自己的属性 JobDataMap.
例如你可以定义一个 "SalesReportJob" 类. 这个类被设计成需要通过 JobDataMap 来输入一个销售经理的名字, 这个销售经理手上有份销售报告. 然后可以构造多个该 job 类的定义, 也就是 JobDetails. 像 "SalesReportForJoe" 或者 "SalesReportForMike", 这两个 JobDetails 分别有 "joe" 和 "mike" 存储在他们相对应的 JobDataMaps 里面, 来作为相应 jobs 的输入.
当一个 trigger 触发时, 与 trigger 相关联的 JobDetail 实例(instance definition) 会被加载进来, 这样 job 类就会被 schedular 中配置好的 JobFactory 初始化了. 默认的 JobFactory 直接调用 Activator.CreateInstance 来初始化, 然后尝试调用 job 类中那些字段名与 JobDataMap 字段相匹配的字段的 setter 方法来设置属性值. 也可以定义自己的 JobFactory 来实现像依赖注入和控制反转这样的 job 实例化.
在这个教程里面, 我们用 "job definition" 或者 "JobDetail instance" 来表示 JobDetail 或者正在执行中的 job. 通常 "job" 就表示一个 job definition 或者JobDetail. 除非有特别说明是指向 Job 类.
Job State and Concurrency
另外, 关于 job 的状态数据 (也就是 JobDataMap) 和同步, 有一些 attributes 可以加到 job 类里面来修饰 Quartz 的这些特性.
DisallowConcurrentExecution 加到 job 类上来指示 Quartz 不要同时执行多个该 job 类的 definition(JobDetail) 在前一个例子里, 如果 "SalesReportJob" 有这个 attribute, 那么在特定时间就只有一个 "SalesReportForJoe" 可以执行, 但是可以同时执行 "SalesReportForMike". 这个约束是对于 JobDetail( Job definition), 而不是 job 类的某个实例.
PersistJobDataAfterExecution 加到 Job 类上用来指示 Quartz 在 Execute 方法成功执行完成后更新 JobDetail 的 JobDataMap (不抛出异常), 这样下一次执行同一个 JobDetail 时, 用的就是更新后的数据而不是一开始就保存的数据.DisallowConcurrentExecution 同样约束 job definition instance,而不是 job 实例,(e.g. 也就是说状态无关需要 execute 方法明确地知道).
强烈建议一起使用 PersistJobDataAfterExecution 和 DisallowConcurrentExecution 来避免可能的race conditions(两个同样的 JobDetail 同时执行时, 他们执行的数据却不一样).
Other Attributes Of Jobs
其他可以约束于 JobDetail 对象的 attributes:
- Durability - 当没有任何其他的 triggers 和 jobDetail 关联时, 自动从 scheduler 中删去 JobDetail. 也就是说该 job 的生命周期取决于它的 triggers.
- RequestsRecovery - 如果一个 job 在 scheduler 强制停止期间执行(i.e. 进程挂掉或者电脑关掉), 那么在 scheduler 重启的时候 job 会重新执行. 这种情况下, JobExecutionContext.Recovering 返回 true.
JobExecutionException
execute 方法只允许抛出 JobExecutionException 异常. 所以建议把整个 excute 方法 try-catch 起来. 另外可以查看 JobExecutionException 的文档, 这样你可以指定各种指令来告诉 scheduler 怎么处理你的异常.