Quartz.NET--JOB作业

来源:互联网 发布:人工智能讲座 编辑:程序博客网 时间:2024/05/17 16:44

作业流程是在调度器的统一调度下完成的,它可以调度多个作业,触发器提供作业执行的条件(每天 8:00 am),触发器与作业关联,它们是 1:N 的关系,1个触发器可以关联1个或多个作业。

附带的作业

      我们知道要实现自己的作业功能只要继承 IJob 接口并实现 Execute(JobExecutionContext context) 方法,再把它添加到调度器,调度器会调用执行Execute(JobExecutionContext context) 方法。调用期间,调度器会跟踪作业和它们的执行次数。Quartz.NET 默认提供了 FileScanJob 监视某个文件是否被修改,NativeJob 执行指定程序,NoOpJob 空操作用来给系统调用 ITriggerListener 、IJobListener ,以及 SendMailJob 邮件发送作业


它有一个存储作业执行时数据的重要属性:MergedJobDataMap,它是 JobDataMap 类型,也可以使用 JobExecutionContext.JobDetail.JobDataMap 来获得 JobDataMap 对象的引用。作为传递参数的容器,JobDataMap 间接继承了 DirtyFlagMap,DirtyFlagMap 内嵌了 Hashtable 容器,它有一组数据读写方法。可以使用 Visual Studio 的类图来查看。



当实现一个真实的作业类时,一些属性是需要告知Quartz的,也是您希望该作业需要有的。这就需要通过JobDetail类,这个类在前面我们已经简单介绍过。

下面我们看一下作业的本质,以及生命周期。还是看看前面的例子:

Lesson1
// construct a scheduler factory ISchedulerFactory schedFact = new StdSchedulerFactory(); // get a scheduler IScheduler sched = schedFact.GetScheduler(); sched.Start(); // construct job info JobDetail jobDetail = new JobDetail("myJob"nulltypeof(DumbJob)); // fire every hour Trigger trigger = TriggerUtils.MakeHourlyTrigger(); // start on the next even hour trigger.StartTime = TriggerUtils.GetEvenHourDate(DateTime.UtcNow); trigger.Name = "myTrigger"; sched.ScheduleJob(jobDetail, trigger);

作业类"DumbJob": 

public class DumbJob : IJob

{

 public DumbJob() { }

 publicvoid Execute(JobExecutionContext context)

 {

   Console.WriteLine("DumbJob is executing.");

 }

}

如上例所示,向scheduler提供JobDetail实例,就能指向作业执行。scheduler每次执行(Execute方法)作业前,都新生成一个作业实例。由于是没有参数的构造器,每次作业执行,数据都没清一次。

  现在也许你会问,如何才能向作业实例提供一些属性/配置呢?在作业执行之间能跟踪作业的状态吗?这些问题的答案是一样的:JobDataMap对象,JobDetail对象的一部分。

JobDataMap

  在作业实例执行的时候,JobDataMap可以用来保存任何数量对象(可序列化)的。JobDataMap继承自IDictionary接口,添加一些便捷的方法,以便存储和获得原型数据。

以下是添加到scheduler之前,向JobDataMap存放数据的代码片段:
jobDetail.JobDataMap["jobSays"] = "Hello World!";
jobDetail.JobDataMap["myFloatValue"] = 3.141f;
jobDetail.JobDataMap["myStateData"] = new ArrayList();

以下是在作业执行时,取数据的代码片段:
public
 class DumbJob : IJob
{
 public void Execute(JobExecutionContext context)
 {
   string instName = context.JobDetail.Name;
   string instGroup = context.JobDetail.Group;
   JobDataMap dataMap = context.JobDetail.JobDataMap;
   string jobSays = dataMap.GetString("jobSays");

   float myFloatValue = dataMap.GetFloat("myFloatValue");
   ArrayList state = (ArrayList) dataMap["myStateData"];
   state.Add(DateTime.UtcNow);
   Console.WriteLine("Instance {0} of DumbJob says: {1}", instName, jobSays);
 }
}
  如果需要用JobStore来做持久化的话,就需要小心存在JobDataMap的是什么了。因为存放的对象是要被序列化的,那么就很容易出现对象的版本问题。显然,.Net的标准数据类型是安全的,除此之外,序列化的对象实例,任何变化,都会导致版本兼容问题。或者干脆只允许原型数据类型和字符串能保存,这样能避免以后序列化版本冲突问题。

在作业执行时,JobDataMap可以从JobExecutionContext中获得。这时候它是JobDetail和Trigger中的JobDataMap合并,是后来同名对象的覆盖。
下面是在作业执行时,从JobExecutionContext中获得JobDataMap的代码片段:
public class DumbJob : IJob
{
    public void Execute(JobExecutionContext context)
    {
        string instName = context.JobDetail.Name;
        string instGroup = context.JobDetail.Group;

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

        string jobSays = dataMap.GetString("jobSays");
        float myFloatValue = dataMap.GetFloat("myFloatValue");
        ArrayList state = (ArrayList) dataMap.Get("myStateData");
        state.Add(DateTime.UtcNow);

        Console.WriteLine("Instance {0} of DumbJob says: {1}", instName, jobSays);
    }
}

有状态与无状态的作业

   以上我们的作业实例都是从 IJob 继承,Quartz.NET 里还有 IStatefulJob 、IInterruptableJob,它的声明方式为:


public interface IStatefulJob : IJob 
    { 
    }

public interface IInterruptableJob : IJob 
    { 
        
void Interrupt(); 
    }

      IInterruptableJob 接口提供了一个中断方法,但是 IStatefulJob 没有自己的方法。从 Quartz.NET 官方了解到:

一个 Job 实例可以被定义为“有状态的”或者“无状态的”。在执行无状态的任务过程中任何对 JobDataMap 所作的更改都将丢失。有状态的任务恰好相反,它在任务的每次执行之后重新存储 JobDataMap 。有状态任务的一个缺点就是它不能并发执行。也就是说,如果任务有状态,那么当触发器试图触发它,触发器就会被阻塞直到前面的执行完成。想使任务有状态,它就要实现 IStatefulJob 接口而不是实现IJob接口。


作业实例,可以被指定为“statefull”,或者“non-statefull”。Non-statefull作业只在JobDataMap添加到scheduler时有数据。就是意味着,在下一次作业执行时,任何作业状态数据(JobDataMap)的修改丢将丢失或看不见。正如您猜测的那样,statefull作业正好相反,作业的每次执行,JobDataMap都会重新保存,这给statefull作业的负作用,就是不能并发执行。也就是说,如果一个statefull作业,触发器已经触发正在执行了,下个触发器将会被阻塞,直到前一个执行完成。
  标识一个statefull作业,只要继承IStatefullJob接口,而不是IJob接口。

Job 'Instances'

  这个需要最后明确的是,您可以创建一个作业类,通过创建多个JobDetail实例,存储在scheduler中,每个实例都有自己的属性集及JobDataMap。
  当与作业关联的触发器触发时,作业通过JobFactory实例化。缺省的JobFactory调用Activator.CreateInstance。

Other Attributes Of Jobs

这里列一下JobDetail对象的其他属性:

  • Durable - 一个non-durable作业, 当没有与之关联的active trigger时,会自动从scheduler中删除的。
  • Volatile - 一个volatile作业, 在re-starts scheduler之间是不保留的。
  • RequestsRecovery - 一个"requests recovery"作业,在scheduler“hard shutdown”的情况下,或者机器断电的时候,当scheduler重启时,作业重新执行。JobExecutionContext.isRecovering()返回true。
  • JobListeners - 作业可以没有或多个JobListeners。作业执行时,listeners会被提醒。更多讨论后面专门讨论。

The Job.Execute(..) Method

  IJob.Execute(..)方法相关细节。只能通过JobExecutionException抛异常。所以需要把整个执行内容放到一个'try-catch' 代码块中。



为什么作业和触发分开

很多作业调度程序,作业和触发没有明显的分开界限。有些作业定义为一个作业识别号和相应的执行时间。还有些更像Quartz.NET作业和触发的聚合。当开发Quartz的时候,Quartz team决定,把调度和所要执行的动作分开(trigger)。据我们所知,这将带来很多便捷。例如:

  • 作业可以独立trigger存储在scheduler中,多个trigger关联同一个作业。
  • 松耦合可以重新调度已经过期作业,而不需要重新定义。也可以修改替换触发器而不需要重新定义与之相关的作业。

识别符Identifiers

与scheduler注册作业和触发器时,需要指定识别名称的。作业、触发器也能够安祖存放,为日后维护时组织分类作业和触发器提供方便。作业和触发器的名称在组内必须是唯一的。也就是说,正确的识别符是名称 + 组,如果让组名(Job or Trigger)留空 'null', 就等于说属于SchedulerConstants.DefaultGroup。