Quartz.NET 2.x教程

第1课:使用Quartz
第2课:工作和触发器
第3课:关于工作和JobDetails的更多信息
第4课:有关触发器的更多信息
第5课:SimpleTriggers
第6课:CronTriggers
第7课:TriggerListeners和JobListeners
第8课:SchedulerListeners
第9课:JobStores
第10课:配置,资源使用和SchedulerFactory
第11课:高级(企业)功能
第12课:其他功能

官方网址:https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/index.html

第1课:使用Quartz

  在使用调度程序之前,需要对其进行实例化(谁已经猜到了?)。为此,您使用ISchedulerFactory的实现者。

一旦调度程序被实例化,它就可以启动,处于待机模式并关闭。请注意,一旦调度程序关闭,就不能在不重新实例化的情况下重新启动它。在调度程序启动之前,触发器不会触发(作业不执行),也不会在处于暂停状态时触发。这是一段代码的快速代码,它实例化并启动一个调度程序,并安排一个作业执行:

使用Quartz.NET

        // 构造一个调度程序工厂
        ISchedulerFactory schedFact = new StdSchedulerFactory();

        // 得到一个调度器
        IScheduler sched = schedFact.GetScheduler();
        sched.Start();

        // 定义作业并将其绑定到HelloJob类
        IJobDetail job = JobBuilder.Create<HelloJob>()
            .WithIdentity("myJob", "group1")
            .Build();

        // 现在触发作业运行,然后每40秒触发一次
        ITrigger trigger = TriggerBuilder.Create()
            .WithIdentity("myTrigger", "group1")
            .StartNow()
            .WithSimpleSchedule(x => x
                .WithIntervalInSeconds(40)
                .RepeatForever())
            .Build();

        sched.ScheduleJob(job, trigger);

如您所见,使用Quartz.NET非常简单。

第2课:工作和触发器

Quartz API

Quartz API的关键接口和类是:

  • IScheduler - 与调度程序交互的主要API。
  • IJob - 由您希望由调度程序执行的组件实现的接口。
  • IJobDetail - 用于定义Jobs的实例。
  • ITrigger - 一个组件,用于定义执行给定作业的计划。
  • JobBuilder - 用于定义/构建JobDetail实例,用于定义Jobs的实例。
  • TriggerBuilder - 用于定义/构建触发器实例。

在本教程中,为了便于阅读,以下术语可互换使用:IScheduler和Scheduler,IJob和Job,IJobDetail和JobDetail,ITrigger和Trigger。

一个调度程序的生命周期是由它为界的创作,通过SchedulerFactoryShutdown()方法的调用。创建后可以使用IScheduler接口添加,删除和列出作业和触发器,以及执行其他与调度相关的操作(例如暂停触发器)。但是,在使用Start()方法启动调度程序之前,调度程序实际上不会对任何触发器执行(执行作业)

Quartz提供了“构建器”类,用于定义域特定语言(或DSL,有时也称为“流畅接口”)。在上一课中,您看到了一个示例,我们再次展示了这里的一部分:

        // 构造一个调度程序工厂
        ISchedulerFactory schedFact = new StdSchedulerFactory();

        // 得到一个调度器
        IScheduler sched = schedFact.GetScheduler();
        sched.Start();

        // 定义作业并将其绑定到HelloJob类
        IJobDetail job = JobBuilder.Create<HelloJob>()
            .WithIdentity("myJob", "group1") //名称“myJob”,组“group1”
            .Build();

        // 现在触发作业运行,然后每40秒触发一次
        ITrigger trigger = TriggerBuilder.Create()
            .WithIdentity("myTrigger", "group1")
            .StartNow()
            .WithSimpleSchedule(x => x
                .WithIntervalInSeconds(40)
                .RepeatForever())
            .Build();

        // 告诉quartz使用我们的触发器来安排任务
        sched.ScheduleJob(job, trigger);

构建作业定义的代码块使用JobBuilder使用流畅的界面来创建产品IJobDetail。同样,构建触发器的代码块使用TriggerBuilder的流畅接口和扩展方法,这些方法特定于给定的触发器类型。可能的计划扩展方法是:

  • WithCalendarIntervalSchedule
  • WithCronSchedule
  • WithDailyTimeIntervalSchedule
  • WithSimpleSchedule

DateBuilder类包含各种方法,可以轻松地为特定时间点构建DateTimeOffset实例(例如表示下一个偶数小时的日期 - 或者换句话说,如果它当前是9:43:27,则为10:00:00)。

工作和触发器

Job是一个实现IJob接口的类,它只有一个简单的方法:

IJob接口

    namespace Quartz
    {
        public interface IJob
        {
            void Execute(JobExecutionContext context);
        }
    }

当Job的触发器触发时(稍后会更多),Execute()方法由调度程序的一个工作线程调用。传递给此方法的JobExecutionContext对象为作业实例提供有关其“运行时”环境的信息 - 执行它的调度程序的句柄,触发执行的触发器的句柄,作业的JobDetail对象以及其他一些项目。

JobDetail对象是在将Job添加到调度程序时由Quartz.NET客户端(您的程序)创建的。它包含Job的各种属性设置,以及JobDataMap,它可用于存储作业类的给定实例的状态信息。它本质上是作业实例的定义,将在下一课中进一步详细讨论。

触发器对象用于触发作业的执行(或“触发”)。当您希望计划作业时,可以实例化触发器并“调整”其属性以提供您希望的计划。触发器也可能有一个与之关联的JobDataMap - 这对于将参数传递给特定于触发器触发的Job非常有用。Quartz附带了一些不同的触发器类型,但最常用的类型是SimpleTrigger(接口ISimpleTrigger)和CronTrigger(接口ICronTrigger)。

如果您需要“一次性”执行(在给定时刻只执行一次作业),或者如果您需要在给定时间触发作业,并且重复N次,延迟,则SimpleTrigger非常方便在执行之间的T. 如果您希望基于类似日历的时间表触发 - 例如“每个星期五,中午”或“每个月的第10天的10:15”,CronTrigger非常有用。

为什么工作和触发?许多作业调度程序没有单独的作业和触发器概念。有些人将“工作”定义为执行时间(或计划)以及一些小作业标识符。其他人很像Quartz的工作和触发对象的结合。在开发Quartz时,我们认为在时间表和要按照该时间表执行的工作之间创建分离是有意义的。这(我们认为)有很多好处。

例如,可以独立于触发器创建作业调度程序中的作业并将其存储在作业调度程序中,并且许多触发器可以与同一作业相关联。此松散耦合的另一个好处是能够配置在关联的触发器到期后保留在调度程序中的作业,以便以后可以重新调度,而无需重新定义它。它还允许您修改或替换触发器,而无需重新定义其关联的作业。

身份

作业和触发器在Quartz调度程序中注册时会被赋予识别密钥。作业和触发器的键(JobKey和TriggerKey)允许将它们放入“组”中,这对于将作业和触发器组织成“报告作业”和“维护作业”等类别非常有用。作业或触发器的键的名称部分在组内必须是唯一的

  • 换句话说,作业或触发器的完整键(或标识符)是名称和组的组合。

你现在有什么作业和触发器,您可以了解更多关于他们的总体思路 第3课:更多关于工作与JobDetails和第4课:更多关于触发器

第3课:关于工作和JobDetails的更多信息

正如您在第2课中看到的那样,工作很容易实现。关于作业的性质,关于IJob接口的Execute()方法和JobDetails,还需要了解更多内容。

虽然您实现的作业类具有知道如何处理特定类型作业的实际工作的代码,但Quartz.NET需要了解您可能希望该作业的实例具有的各种属性。这是通过JobDetail类完成的,在上一节中已经简要提到过。

JobDetail实例是使用JobBuilder类构建的。JobBuilder允许您使用流畅的界面描述您的工作细节。

现在让我们花点时间讨论一下Quartz.NET中作业的“本质”和作业实例的生命周期。首先让我们回顾一下我们在第1课中看到的一些代码片段:

使用Quartz.NET

        // 定义作业并将其绑定到HelloJob类
        IJobDetail job = JobBuilder.Create<HelloJob>()
            .WithIdentity("myJob", "group1") //名称“myJob”,组“group1”
            .Build();

        // 现在触发作业运行,然后每40秒触发一次
        ITrigger trigger = TriggerBuilder.Create()
            .WithIdentity("myTrigger", "group1")
            .StartNow()
            .WithSimpleSchedule(x => x
                .WithIntervalInSeconds(40)
                .RepeatForever())
            .Build();

        // 告诉quartz使用我们的触发器来安排任务
        sched.ScheduleJob(job, trigger);

现在考虑将作业类HelloJob 定义为:

    public class HelloJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            Console.WriteLine("HelloJob is executing.");
        }
    }

请注意,我们为调度程序提供了一个IJobDetail实例,并且它通过简单地提供作业的类来引用要执行的作业。调度程序执行作业的每个(和每个)时间,它在调用其Execute(..)方法之前创建该类的新实例。这种行为的一个后果是,工作必须有一个无争议的构造函数。另一个分支是在作业类上定义数据字段没有意义 - 因为它们的值不会在作业执行之间保留。

您现在可能想问“我如何为Job实例提供属性/配置?”和“如何在执行之间跟踪作业的状态?”这些问题的答案是相同的:关键是JobDataMap ,它是JobDetail对象的一部分

JobDataMap的

JobDataMap可用于保存您希望在作业实例执行时可用的任意数量(可序列化)对象。JobDataMap是IDictionary接口的一个实现,并且具有一些用于存储和检索基本类型数据的便利方法。

以下是在将作业添加到调度程序之前将数据放入JobDataMap的一些快速代码段:

在JobDataMap中设置值

    // 定义作业并将其绑定到DumbJob类
    IJobDetail job = JobBuilder.Create<DumbJob>()
        .WithIdentity("myJob", "group1") //名称“myJob”,组“group1”
        .UsingJobData("jobSays", "Hello World!")
        .UsingJobData("myFloatValue", 3.141f)
        .Build();

以下是在作业执行期间从JobDataMap获取数据的快速示例:

从JobDataMap获取值

        public void Execute(IJobExecutionContext 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(在本教程的JobStore部分中讨论),您应该谨慎地决定放置在JobDataMap中的内容,因为其中的对象将被序列化,因此它们容易出现类版本问题。显然,标准的.NET类型应该是非常安全的,但除此之外,每当有人更改您已为其序列化实例的类的定义时,必须注意不要破坏兼容性。

或者,您可以将AdoJobStore和JobDataMap置于只能在地图中存储基元和字符串的模式,从而消除以后序列化问题的任何可能性。

如果向作业类添加与JobDataMap中的键名相对应的set accessor属性,那么Quartz的默认JobFactory实现将在作业实例化时自动调用这些setter,从而无需显式获取值。在您的execute方法中映射。

触发器还可以具有与之关联的JobDataMaps。如果您有一个存储在调度程序中的作业以供多个触发器定期/重复使用,但是每次独立触发,您希望为作业提供不同的数据输入,这将非常有用。

在作业执行期间在JobExecutionContext上找到的JobDataMap可以方便起见。它是在JobDetail上找到的JobDataMap和在Trigger上找到的JobDataMap的合并,后者中的值覆盖前者中的任何同名值。

以下是在作业执行期间从JobExecutionContext的合并JobDataMap获取数据的快速示例:

    public class DumbJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            JobKey key = context.JobDetail.Key;

            JobDataMap dataMap = context.MergedJobDataMap;  // 请注意与前面示例的不同之处

            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将数据映射值“注入”到您的类中,它可能看起来像这样:

    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;  // 请注意与前面示例的不同之处

            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()方法中的代码更清晰。人们还可以争辩说,虽然代码更长,但实际上编程更少,如果程序员的IDE用于自动生成属性,而不是手动编写单个调用以从JobDataMap检索值。这是你的选择。

工作“实例”

许多用户花时间对确切构成“作业实例”的内容感到困惑。我们将在这里和下面关于作业状态和并发性的部分中尝试清除它。

您可以创建一个作业类,并通过创建多个JobDetails实例在调度程序中存储它的许多“实例定义”

  • 每个都有自己的属性和JobDataMap - 并将它们全部添加到调度程序。

例如,您可以创建一个实现名为“SalesReportJob”的IJob接口的类。可以对作业进行编码,以期望发送给它的参数(通过JobDataMap)指定销售报告应基于的销售人员的姓名。然后,他们可以创建作业的多个定义(JobDetails),例如“SalesReportForJoe”和“SalesReportForMike”,其在相应的JobDataMaps中指定“joe”和“mike”作为相应作业的输入。

触发器触发时,将加载与之关联的JobDetail(实例定义),并通过Scheduler上配置的JobFactory实例化其引用的作业类。默认的JobFactory使用Activator.CreateInstance简单地调用作业类的默认构造函数,然后尝试在类上调用与JobDataMap中的键名匹配的setter属性。您可能希望创建自己的JobFactory实现来完成诸如让应用程序的IoC或DI容器生成/初始化作业实例之类的事情。

在“Quartz speak”中,我们将每个存储的JobDetail称为“作业定义”或“JobDetail实例”,并且我们将每个执行作业称为“作业实例”或“作业定义的实例”。通常,如果我们只使用“job”这个词,我们指的是命名定义或JobDetail。当我们提到实现作业接口的类时,我们通常使用术语“作业类型”。

作业状态和并发

现在,关于作业的状态数据(也称为JobDataMap)和并发性的一些附加说明。有几个属性可以添加到您的Job类中,这些属性会影响Quartz在这些方面的行为。

DisallowConcurrentExecution是一个可以添加到Job类的属性,它告诉Quartz不要同时执行给定作业定义的多个实例(指向给定的作业类)。注意那里的措辞,因为它是非常谨慎地选择的。在上一节的示例中,如果“SalesReportJob”具有此属性,则只能在给定时间执行“SalesReportForJoe”的一个实例,但它可以与“SalesReportForMike”实例同时执行。约束基于实例定义(JobDetail),而不是基于作业类的实例。然而,决定(在Quartz的设计期间)具有类本身所承载的属性,因为它经常对类的编码方式产生影响。

PersistJobDataAfterExecution是一个可以添加到Job类的属性,它告诉Quartz在Execute()方法成功完成后(不抛出异常)更新JobDetail的JobDataMap的存储副本,以便下一次执行相同的作业(JobDetail) )接收更新的值而不是原始存储的值。DisallowConcurrentExecution属性类似,这适用于作业定义实例,而不是作业类实例,尽管决定让作业类携带属性,因为它通常会对类的编码方式产生影响(例如'有状态'将需要由execute方法中的代码明确地“理解”。

如果使用PersistJobDataAfterExecution属性,则应强烈考虑使用DisallowConcurrentExecution属性,以避免在同时执行同一作业(JobDetail)的两个实例时可能存在的数据混乱(竞争条件)。

工作的其他属性

以下是可以通过JobDetail对象为作业实例定义的其他属性的快速摘要:

  • 持久性 - 如果作业非持久性,一旦不再有与之关联的活动触发器,它将自动从调度程序中删除。换句话说,非持久性工作的寿命由其触发器的存在所限制。
  • RequestsRecovery - 如果一个作业“请求恢复”,并且它正在调度程序的“硬关闭”期间执行(即它在崩溃中运行的进程,或者机器被关闭),那么它将被重新执行当调度程序再次启动时。在这种情况下,JobExecutionContext.Recovering属性将返回true。

JobExecutionException

最后,我们需要告诉您IJob.Execute(..)方法的一些细节。您应该从execute方法中抛出的唯一异常类型是JobExecutionException。因此,您通常应该使用'try-catch'块来包装execute方法的全部内容。您还应该花一些时间查看JobExecutionException的文档,因为您的作业可以使用它来为调度程序提供有关如何处理异常的各种指令。

第4课:有关触发器的更多信息

与作业一样,触发器相对容易使用,但是在您可以充分利用Quartz.NET之前,确实需要了解和理解各种可自定义的选项。此外,如前所述,有不同类型的触发器,您可以选择这些触发器以满足不同的调度需求。

常见触发器属性

除了所有触发器类型都具有用于跟踪其身份的TriggerKey属性之外,还有许多其他属性对所有触发器类型都是通用的。在构建触发器定义时,使用TriggerBuilder设置这些常用属性(后面将举例说明)。

以下是所有触发器类型共有的属性列表:

  • JobKey属性指示应触发火灾时,执行作业的身份。
  • StartTimeUtc属性指示当触发的时间表首先进入的影响。该值是DateTimeOffset对象,用于定义给定日历日期的时刻。对于某些触发类型,触发器实际上会在开始时触发,对于其他触发器类型,它只是标记应该开始遵循调度的时间。这意味着您可以存储具有计划的触发器,例如1月份的“每月的第5天”,如果StartTimeUtc属性设置为4月1日,则将在第一次触发之前的几个月。
  • EndTimeUtc属性指示当触发的时间表应该不再有效。换句话说,具有“每月的第5天”和7月1日结束时间表的触发器将在6月5日的最后一次触发。

其他属性需要更多解释,将在以下小节中讨论。

优先

有时,当您有许多触发器(或Quartz.NET线程池中的少数工作线程)时,Quartz.NET可能没有足够的资源来立即触发计划同时触发的所有触发器。在这种情况下,您可能希望控制哪些触发器在可用的Quartz.NET工作线程中首先破解。为此,您可以在Trigger上设置priority属性。如果N触发器同时触发,但当前只有Z工作线程可用,则首先执行具有最高优先级的第一个Z触发器。如果未在触发器上设置优先级,则它将使用默认优先级5.任何整数值都允许优先级,正或负。

注意:仅当触发器具有相同的触发时间时才会比较优先级。计划在10:59开火的触发器将始终在计划在11:00开火之前开火。

注意:当检测到触发器的作业需要恢复时,其恢复的调度优先级与原始触发器相同。

失火说明

触发器的另一个重要特性是它的“失火指令”。如果持久性触发器由于调度程序被关闭而“错过”其触发时间,或者因为Quartz.NET的线程池中没有可用于执行作业的线程,则会发生失败。不同的触发类型可以使用不同的失火指令。默认情况下,它们使用“智能策略”指令 - 该指令具有基于触发类型和配置的动态行为。当调度程序启动时,它会搜索任何已失效的持久触发器,然后根据其单独配置的失火指令更新每个触发器。当您在自己的项目中开始使用Quartz.NET时,您应该熟悉在给定触发器类型上定义的失火指令,并在其API文档中进行了解释。有关失火指令的更多具体信息将在针对每种触发类型的教程课程中给出。

日历

实现ICalendar接口的Quartz.NET Calendar对象可以在触发器存储在调度程序中时与触发器相关联。日历可用于从触发器的触发计划中排除时间块。例如,您可以创建一个触发器,在每个工作日上午9:30触发作业,但随后添加一个排除所有业务假期的日历。

Calendar可以是任何实现ICalendar接口的可序列化对象,如下所示:

namespace Quartz
{
    public interface ICalendar
    {
        string Description { get; set; }

        ICalendar CalendarBase { set; get; }

        bool IsTimeIncluded(DateTimeOffset timeUtc);

        DateTime GetNextIncludedTimeUtc(DateTimeOffset timeUtc);
    }
} 

即使日历可以“阻挡”时间段只有一毫秒,但最有可能的是,你会对整天的“封锁”感兴趣。为方便起见,Quartz.NET包含了类HolidayCalendar,它就是这样做的。

必须通过AddCalendar(..)方法实例化日历并向调度程序注册日历。如果您使用HolidayCalendar,则在实例化之后,您应该使用其AddExcludedDate(DateTime date)方法,以便使用您希望从计划中排除的日期填充它。相同的日历实例可以与多个触发器一起使用,例如:

日历示例

    HolidayCalendar cal = new HolidayCalendar();
    cal.AddExcludedDate(someDate);
    
    sched.AddCalendar("myHolidays", cal, false);
    
    ITrigger t = TriggerBuilder.Create()
        .WithIdentity("myTrigger")
        .ForJob("myJob")
        .WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
        .ModifiedByCalendar("myHolidays") // but not on holidays
        .Build();

    // .. schedule job with trigger

    ITrigger t2 = TriggerBuilder.Create()
        .WithIdentity("myTrigger2")
        .ForJob("myJob2")
        .WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(11, 30)) // execute job daily at 11:30
        .ModifiedByCalendar("myHolidays") // but not on holidays
        .Build();
    
    // .. schedule job with trigger2 

触发器的构建/构建的细节将在接下来的几节课中给出。现在,只需要相信上面的代码创建了两个触发器,每个触发器计划每天触发。但是,将跳过在日历排除的期间内发生的任何发射。

有关可能满足您需求的许多ICalendar实现,请参阅Quartz.Impl.Calendar命名空间。

第5课:SimpleTrigger

如果您需要在特定时刻执行一次作业,或者在特定时刻执行作业,然后按特定时间间隔重复,则SimpleTrigger应满足您的日程安排需求。或者更简洁的英语,如果你想要触发器在2005年1月13日上午11:23:54发射,然后每10秒再发射5次。

通过此描述,您可能不会发现SimpleTrigger的属性包括:开始时间,结束时间,重复计数和重复间隔,这一点令人惊讶。所有这些属性正是您所期望的那样,只有一些与end-time属性相关的特殊注释。

重复计数可以是零,正整数或常量值SimpleTrigger.RepeatIndefinitely。重复间隔属性必须是TimeSpan.Zero或正TimeSpan值。请注意,重复间隔为零将导致触发器的“重复计数”触发同时发生(或者与调度程序可以管理的同时接近)。

如果您还不熟悉DateTime类,您可能会发现它有助于计算触发器触发时间,具体取决于您尝试创建的startTimeUtc(或endTimeUtc)。

EndTimeUtc属性(如果已指定)覆盖重复计数属性。如果您希望创建一个触发器(例如在给定时刻每10秒触发一次触发器),而不是必须计算它在开始时间和结束时间之间重复的次数,那么这可能很有用。可以简单地指定结束时间,然后使用RepeatIndefinitely的重复计数(你甚至可以指定一个巨大数字的重复计数,肯定会超过触发器在结束时间到来之前实际触发的次数) 。

SimpleTrigger实例是使用TriggerBuilder(用于触发器的主要属性)和WithSimpleSchedule扩展方法(用于SimpleTrigger特定属性)构建的。

在特定时刻构建触发器,不重复:

// 触发器生成器默认创建简单的触发器,实际上返回一个ITrigger
ISimpleTrigger trigger = (ISimpleTrigger) TriggerBuilder.Create()
    .WithIdentity("trigger1", "group1")
    .StartAt(myStartTime) // 一些日期
    .ForJob("job1", "group1") // 用名称、组字符串标识作业
    .Build();

在特定时刻构建触发器,然后每十秒重复十次:

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .StartAt(myTimeToStartFiring) // 如果没有给出起始时间(如果省略了这一行),则隐含“now”
    .WithSimpleSchedule(x => x
        .WithIntervalInSeconds(10)
        .WithRepeatCount(10)) // 注意,10次重复将导致总共11次解雇
    .ForJob(myJob) // 将作业标识为其JobDetail本身的句柄
    .Build();

构建一个触发器,将在未来五分钟内触发:

trigger = (ISimpleTrigger) TriggerBuilder.Create()
    .WithIdentity("trigger5", "group1")
    .StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute)) // 使用DateBuilder在将来创建一个日期
    .ForJob(myJobKey) // 将job与其JobKey标识
    .Build();

构建一个现在将触发的触发器,然后每隔五分钟重复一次,直到22:00:

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger7", "group1")
    .WithSimpleSchedule(x => x
        .WithIntervalInMinutes(5)
        .RepeatForever())
    .EndAt(DateBuilder.DateOf(22, 0, 0))
    .Build();

构建一个触发器,在下一个小时的顶部触发,然后每2个小时重复一次,永远:

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger8") // 因为没有指定组,所以“trigger8”将在默认组中
    .StartAt(DateBuilder.EvenHourDate(null)) // 获得下一个偶数小时(分和秒0(“00:00”))
    .WithSimpleSchedule(x => x
        .WithIntervalInHours(2)
        .RepeatForever())
    // 注意,在这个示例中,没有调用'forJob(..)'
    // 如果触发器连同作业一起传递给调度程序,那么哪个是有效的
    .Build();
scheduler.scheduleJob(trigger, job);

花一些时间看所有的由所定义的语言可用的方法TriggerBuilder及其扩展方法WithSimpleSchedule,这样你可以熟悉可能没有在上面的例子证明提供给您的选项。

SimpleTrigger失火说明

SimpleTrigger有几条指令可用于告知Quartz.NET发生失火时它应该做什么。(在本教程的“更多关于触发器”部分中介绍了失火情况)。这些指令在MisfirePolicy.SimpleTrigger上定义为常量(包括描述其行为的API文档)。说明包括:

SimpleTrigger的Misfire指令常量

  • MisfireInstruction.IgnoreMisfirePolicy
  • MisfirePolicy.SimpleTrigger.FireNow
  • MisfirePolicy.SimpleTrigger.RescheduleNowWithExistingRepeatCount
  • MisfirePolicy.SimpleTrigger.RescheduleNowWithRemainingRepeatCount
  • MisfirePolicy.SimpleTrigger.RescheduleNextWithRemainingCount
  • MisfirePolicy.SimpleTrigger.RescheduleNextWithExistingCount

您应该从早期的课程中回忆一下,所有触发器都有可用的MisfirePolicy.SmartPolicy指令,并且此指令也是所有触发器类型的默认指令。

如果使用“智能策略”指令,SimpleTrigger将根据给定SimpleTrigger实例的配置和状态在其各种MISFIRE指令之间动态选择。SimpleTrigger.UpdateAfterMisfire()方法的文档解释了此动态行为的确切详细信息。

构建SimpleTriggers时,将misfire指令指定为简单计划的一部分(通过SimpleSchedulerBuilder):

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger7", "group1")
    .WithSimpleSchedule(x => x
        .WithIntervalInMinutes(5)
        .RepeatForever()
        .WithMisfireHandlingInstructionNextWithExistingCount())
    .Build();

第6课:CronTrigger

CronTriggers通常比SimpleTrigger更有用,如果您需要基于类似日历的概念而不是在SimpleTrigger的确切指定间隔上重复发生的作业计划。

使用CronTrigger,您可以指定触发时间表,例如“每周五中午”,或“每个工作日和上午9:30”,甚至“每周一,周三上午9:00到上午10:00之间每隔5分钟”和星期五”。

即便如此,像SimpleTrigger一样,CronTrigger有一个startTime,用于指定调度的有效时间,以及一个(可选的)endTime,用于指定何时停止调度。

Cron表达式

Cron-Expressions用于配置CronTrigger的实例。Cron-Expressions实际上是由七个子表达式组成的字符串,用于描述计划的各个细节。这些子表达式用空格分隔,并表示:

    1. 分钟
    1. 小时
    1. 日的日
    1. 某一天的周
    1. 年(可选字段)

完整的cron表达式的一个例子是字符串“0 0 12?* WED“ - 表示”每周三中午12点“。

各个子表达式可以包含范围和/或列表。例如,前一周中的星期字段(其中显示“WED”)示例可以替换为“MON-FRI”,“MON,WED,FRI”或甚至“MON-WED,SAT”。

通配符(' '字符)可用于表示该字段的“每个”可能值。因此,前一个例子的“月”字段中的' '字符仅表示“每个月”。周日字段中的'*'显然意味着“一周中的每一天”。

所有字段都有一组可以指定的有效值。这些值应该相当明显 - 例如秒和分钟的数字0到59,以及小时的值0到23。每月的日期可以是0-31的任何值,但您需要注意一个月内的天数!月份可以指定为0到11之间的值,或者使用字符串JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV和DEC。星期几可以指定为1到7之间的值(1 =星期日)或使用字符串SUN,MON,TUE,WED,THU,FRI和SAT。

'/'字符可用于指定值的增量。例如,如果在“分钟”字段中输入“0/15”,则表示“每隔15分钟,从零分钟开始”。如果您在“分钟”字段中使用“3/20”,则表示“在每小时每20分钟,从第3分钟开始” - 或者换句话说,它与在分钟中指定“3,23,43”相同领域。

'?' 允许使用字符表示日期和星期几字段。它用于指定“无特定值”。当您需要在两个字段之一中指定某些内容而不是另一个字段时,这非常有用。请参阅下面的示例(和CronTrigger API文档)以获得说明。

“L”字符允许用于日期和星期几字段。这个字符是“last”的简称,但它在两个字段的每一个中都有不同的含义。例如,日期字段中的值“L”表示“月份的最后一天” - 1月31日,非闰年2月28日。如果在星期几字段中单独使用,则仅表示“7”或“SAT”。但如果在星期几字段之后使用另一个值,则表示“该月的最后一个xxx日” - 例如“6L”或“FRIL”均表示“该月的最后一个星期五”。使用“L”选项时,重要的是不要指定列表或值范围,因为您会得到令人困惑的结果。

'W'用于指定最接近给定日期的工作日(周一至周五)。例如,如果您指定“15W”作为日期字段的值,则含义为:“最近的工作日到该月的15日”。

'#'用于指定该月的“第n个”XXX周日。例如,星期几字段中的“6#3”或“FRI#3”的值表示“该月的第三个星期五”。

示例Cron表达式

以下是表达式及其含义的更多示例 - 您可以在CronTrigger的API文档中找到更多

CronTrigger示例1 - 用于创建触发器的表达式,该触发器每5分钟触发一次

"0 0/5 * * * ?"

CronTrigger示例2 - 用于创建触发器的表达式,该触发器在每分钟10秒后(即上午10:00:10,上午10:05:10等)每5分钟触发一次。

"10 0/5 * * * ?"

CronTrigger示例3 - 用于创建触发器的表达式,该触发器在每周三和周五的10:30,11:30,12:30和13:30触发。

"0 30 10-13 ? * WED,FRI"

CronTrigger示例4 - 一个表达式,用于创建触发器,在每个月的5号和20号上午8点到10点之间每隔半小时触发一次。请注意,触发器不会在上午10:00,即8:00,8:30,9:00和9:30触发

"0 0/30 8-9 5,20 * ?"

请注意,某些计划要求过于复杂,无法通过单个触发器表达 - 例如“上午9:00到上午10:00之间每隔5分钟,下午1:00到晚上10:00之间每20分钟”。这种情况下的解决方案是简单地创建两个触发器,并注册它们以运行相同的作业。

建立CronTriggers

CronTrigger实例是使用TriggerBuilder(用于触发器的主要属性)和WithCronSchedule扩展方法(用于特定于CronTrigger的属性)构建的。

您还可以使用CronScheduleBuilder的静态方法来创建计划。

建立一个触发器,每天上午8点到下午5点之间每隔一分钟触发一次:

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .WithCronSchedule("0 0/2 8-17 * * ?")
    .ForJob("myJob", "group1")
    .Build();

构建一个触发器,每天上午10:42开火:

// 这里我们使用CronScheduleBuilder的静态助手方法
trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .WithSchedule(CronScheduleBuilder.DailyAtHourAndMinute(10, 42))
    .ForJob(myJobKey)
    .Build();

要么 -

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .WithCronSchedule("0 42 10 * * ?")
    .ForJob("myJob", "group1")
    .Build();

构建一个触发器,该触发器将在星期三上午10点42分在TimeZone中触发,而不是系统的默认值:

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .WithSchedule(CronScheduleBuilder
        .WeeklyOnDayAndHourAndMinute(DayOfWeek.Wednesday, 10, 42)
        .InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
    .ForJob(myJobKey)
    .Build();

要么 -

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .WithCronSchedule("0 42 10 ? * WED", x => x
        .InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Central America Standard Time")))
    .ForJob(myJobKey)
    .Build();

CronTrigger失火说明

以下说明可用于告知Quartz当CronTrigger发生失火时它应该做什么。(在本教程的“更多关于触发器”部分中介绍了失火情况)。这些指令以常量形式定义(API文档中包含其行为的描述)。说明包括:

  • MisfireInstruction.IgnoreMisfirePolicy
  • MisfireInstruction.CronTrigger.DoNothing
  • MisfireInstruction.CronTrigger.FireOnceNow

所有触发器都可以使用MisfireInstrution.SmartPolicy指令,该指令也是所有触发器类型的默认指令。CronTrigger将“智能策略”指令解释为MisfireInstruction.CronTrigger.FireOnceNow。CronTrigger.UpdateAfterMisfire()方法的API文档解释了此行为的确切详细信息。

在构建CronTriggers时,您将失火指令指定为cron调度的一部分(通过WithCronSchedule扩展方法):

trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .WithCronSchedule("0 0/2 8-17 * * ?", x => x
        .WithMisfireHandlingInstructionFireAndProceed())
    .ForJob("myJob", "group1")
    .Build();

第7课:TriggerListeners和JobListeners

侦听器是您创建的对象,用于根据调度程序中发生的事件执行操作。正如您可能猜到的,TriggerListeners接收与触发器相关的事件,JobListeners接收与作业相关的事件。

与触发器相关的事件包括:触发器触发,触发错误触发(在本文档的“触发器”部分中讨论)和触发器完成(触发器触发的作业完成)。

ITriggerListener接口

public interface ITriggerListener
{
     string Name { get; }
     
     void TriggerFired(ITrigger trigger, IJobExecutionContext context);
     
     bool VetoJobExecution(ITrigger trigger, IJobExecutionContext context);
     
     void TriggerMisfired(ITrigger trigger);
     
     void TriggerComplete(ITrigger trigger, IJobExecutionContext context, int triggerInstructionCode);
}

与作业相关的事件包括:作业即将执行的通知,以及作业完成执行时的通知。

IJobListener接口

public interface IJobListener
{
    string Name { get; }

    void JobToBeExecuted(IJobExecutionContext context);

    void JobExecutionVetoed(IJobExecutionContext context);

    void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException);
} 

使用自己的听众

要创建一个监听器,只需创建一个ITriggerListener和/或IJobListener接口实现的对象。然后,监听器在运行时向调度程序注册,并且必须给出一个名称(或者更确切地说,他们必须通过其Name属性来通告自己的名称。

为方便起见,您的类可以扩展JobListenerSupport或TriggerListenerSupport类,而不是实现这些接口,而只是覆盖您感兴趣的事件。

监听器与调度程序的ListenerManager以及Matcher一起注册,该Matcher描述了监听器想要接收事件的作业/触发器。

监听器在运行时在调度程序中注册,并且不与作业和触发器一起存储在JobStore中。这是因为侦听器通常是与您的应用程序的集成点。因此,每次运行应用程序时,都需要使用调度程序重新注册侦听器。

添加对特定作业感兴趣的JobListener:

scheduler.ListenerManager.AddJobListener(myJobListener, KeyMatcher<JobKey>.KeyEquals(new JobKey("myJobName", "myJobGroup")));

添加对特定组的所有作业感兴趣的JobListener:

scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.GroupEquals("myJobGroup"));

添加对两个特定组的所有作业感兴趣的JobListener:

scheduler.ListenerManager.AddJobListener(myJobListener,
    OrMatcher<JobKey>.Or(GroupMatcher<JobKey>.GroupEquals("myJobGroup"), GroupMatcher<JobKey>.GroupEquals("yourGroup")));

添加对所有作业感兴趣的JobListener:

scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.AnyGroup());

Quartz.NET的大多数用户都没有使用监听器,但是当应用程序需求创建了事件通知的需要时,监听器就很方便,而Job本身并没有明确地通知应用程序。

第8课:SchedulerListeners

SchedulerListeners非常类似于ITriggerListeners和IJobListeners,除了它们接收调度程序本身内事件的通知 - 不一定是与特定触发器或作业相关的事件。

与调度程序相关的事件包括:添加作业/触发器,删除作业/触发器,调度程序中的严重错误,调度程序关闭的通知等。

ISchedulerListener接口

public interface ISchedulerListener
{
    void JobScheduled(Trigger trigger);

    void JobUnscheduled(string triggerName, string triggerGroup);

    void TriggerFinalized(Trigger trigger);

    void TriggersPaused(string triggerName, string triggerGroup);

    void TriggersResumed(string triggerName, string triggerGroup);

    void JobsPaused(string jobName, string jobGroup);

    void JobsResumed(string jobName, string jobGroup);

    void SchedulerError(string msg, SchedulerException cause);

    void SchedulerShutdown();
} 

SchedulerListeners在调度程序的ListenerManager中注册。SchedulerListeners几乎可以是任何实现ISchedulerListener接口的对象。

添加SchedulerListener:

scheduler.ListenerManager.AddSchedulerListener(mySchedListener);

删除SchedulerListener:

scheduler.ListenerManager.RemoveSchedulerListener(mySchedListener);

第9课:JobStores

JobStore负责跟踪您为调度程序提供的所有“工作数据”:作业,触发器,日历等。为Quartz调度程序实例选择适当的IJobStore实现是一个重要的步骤。幸运的是,一旦你理解了它们之间的差异,选择应该是一个非常简单的选择。您声明您的调度程序应在您提供给用于生成调度程序实例的SchedulerFactory的属性文件(或对象)中使用哪个JobStore(以及它的配置设置)。

切勿在代码中直接使用JobStore实例。出于某种原因,许多人试图这样做。JobStore用于Quartz本身的幕后使用。您必须告诉Quartz(通过配置)要使用哪个JobStore,但是您应该只使用代码中的Scheduler接口。

RAMJobStore

RAMJobStore是最简单的JobStore,它也是性能最高的(就CPU时间而言)。RAMJobStore以明显的方式得名:它将所有数据保存在RAM中。这就是为什么它闪电般快速,以及为什么配置如此简单。缺点是,当您的应用程序结束(或崩溃)时,所有调度信息都将丢失 - 这意味着RAMJobStore无法遵守作业和触发器上的“非易失性”设置。对于某些应用程序,这是可以接受的 - 甚至是期望的行为,但对于其他应用程序,这可能是灾难性的。

配置Quartz以使用RAMJobStore

quartz.jobStore.type = Quartz.Simpl.RAMJobStore, Quartz

要使用RAMJobStore(并假设您正在使用StdSchedulerFactory),您不需要做任何特殊的事情。Quartz.NET的默认配置使用RAMJobStore作为工作存储实现。

ADO.NET Job Store(AdoJobStore)

AdoJobStore也被恰当地命名 - 它通过ADO.NET将所有数据保存在数据库中。因此,配置比RAMJobStore要复杂一些,而且速度也不快。但是,性能缺点并不是非常糟糕,特别是如果您使用主键上的索引构建数据库表。

要使用AdoJobStore,必须首先为Quartz.NET创建一组数据库表以供使用。您可以在Quartz.NET发行版的“database / dbtables”目录中找到表创建SQL脚本。如果还没有适用于您的数据库类型的脚本,只需查看其中一个现有脚本,然后以您的数据库所需的任何方式对其进行修改。需要注意的一点是,在这些脚本中,所有表都以前缀“QRTZ_”开头,例如表“QRTZ_TRIGGERS”和“QRTZ_JOB_DETAIL”。这个前缀实际上可以是你想要的任何东西,只要你告诉AdoJobStore前缀是什么(在你的Quartz.NET属性中)。对于在同一数据库内为多个调度程序实例创建多组表,使用不同的前缀可能很有用。

目前,作业存储的内部实现的唯一选择是JobStoreTX,它自己创建事务。这与Quartz的Java版本不同,后者还可以选择使用J2EE容器管理事务的JobStoreCMT。

最后一个难题是设置一个数据源,AdoJobStore可以从中获取与数据库的连接。数据源在Quartz.NET属性中定义。数据源信息包含连接字符串和ADO.NET委托信息。

配置Quartz以使用JobStoreTx

quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz

接下来,您需要为JobStore选择要使用的IDriverDelegate实现。DriverDelegate负责执行特定数据库可能需要的任何ADO.NET工作。StdAdoDelegate是一个使用“vanilla”ADO.NET代码(和SQL语句)来完成其工作的委托。如果没有专门为您的数据库制作的另一个委托,请尝试使用此委托 - 特殊委托通常具有更好的性能或解决方法来解决数据库特定问题。其他委托可以在“Quartz.Impl.AdoJobStore”命名空间中找到,也可以在其子命名空间中找到。

注意:如果您使用默认的StdAdoDelegate,Quartz.NET将发出警告,因为当您有大量的触发器可供选择时,它的性能很差。特定委托具有特殊的SQL来限制结果集长度(SQLServerDelegate使用TOP n,PostgreSQLDelegate LIMIT n,OracleDelegate ROWCOUNT()<= n等)。

选择委托后,将其类名设置为AdoJobStore要使用的委托。

配置AdoJobStore以使用DriverDelegate

quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz

接下来,您需要通知JobStore您正在使用的表前缀(如上所述)。

使用表前缀配置AdoJobStore

quartz.jobStore.tablePrefix = QRTZ_

最后,您需要设置JobStore应该使用哪个数据源。还必须在Quartz属性中定义指定的数据源。在这种情况下,我们指定Quartz应该使用数据源名称“myDS”(在配置属性中的其他位置定义)。

使用要使用的数据源的名称配置AdoJobStore

quartz.jobStore.dataSource = myDS

配置所需的最后一件事是设置数据源连接字符串信息和数据库提供程序。连接字符串是标准的ADO.NET连接,它是特定于驱动程序的。数据库提供程序是数据库驱动程序的抽象,用于在数据库驱动程序和Quartz之间创建松耦合。

设置数据源的连接字符串和数据库提供程序

quartz.dataSource.myDS.connectionString = Server=localhost;Database=quartz;Uid=quartznet;Pwd=quartznet
 quartz.dataSource.myDS.provider = MySql-50

目前支持以下数据库提供程序:

  • SqlServer-20 - .NET Framework 2.0的SQL Server驱动程序
  • OracleODP-20 - Oracle的Oracle驱动程序
  • OracleODPManaged-1123-40 Oracle的Oracle 11托管驱动程序
  • OracleODPManaged-1211-40 Oracle的Oracle 12托管驱动程序
  • MySql-50 - MySQL Connector / .NET v.5.0(.NET 2.0)
  • MySql-51 - MySQL Connector /:NET v.5.1(.NET 2.0)
  • MySql-65 - MySQL Connector /:NET v.6.5(.NET 2.0)
  • SQLite-10 - SQLite ADO.NET 2.0 Provider v.1.0.56(.NET 2.0)
  • Firebird-201 - Firebird ADO.NET 2.0 Provider v.2.0.1(.NET 2.0)
  • Firebird-210 - Firebird ADO.NET 2.0 Provider v.2.1.0(.NET 2.0)
  • Npgsql-20 - PostgreSQL Npgsql

如果有更新的驱动程序,您可以而且应该使用最新版本的驱动程序,只需创建一个程序集绑定重定向

如果您的调度程序非常繁忙(即几乎总是执行与线程池大小相同的作业数,那么您应该将数据源中的连接数设置为大约线程池的大小+ 1。这通常在ADO.NET连接字符串中配置 - 有关详细信息,请参阅驱动程序实现。

“quartz.jobStore.useProperties”配置参数可以设置为“true”(默认为false),以指示AdoJobStore JobDataMaps中的所有值都是字符串,因此可以存储为名称 - 值对,而不是存储BLOB列中序列化形式的更复杂对象。从长远来看,这样更安全,因为您可以避免将非String类序列化为BLOB时出现的类版本问题。

配置AdoJobStore以将字符串用作JobDataMap值(推荐)

quartz.jobStore.useProperties = true

第10课:配置,资源使用和SchedulerFactory

Quartz以模块化方式构建,因此要使其运行,需要将几个组件“拼接”在一起。幸运的是,有一些助手可以实现这一目标。

在Quartz可以完成其工作之前需要配置的主要组件是:

  • 线程池
  • 作业存储
  • 数据源(如有必要)
  • 调度程序本身

ThreadPool为Quartz提供了一组在执行Jobs时使用的Threads。池中的线程越多,可以并发运行的作业数量就越多。但是,太多线程可能会使您的系统陷入困境。大多数Quartz用户发现5个左右的线程很多 - 因为他们在任何给定时间的作业少于100个,这些作业通常不会同时运行,并且作业很短暂(快速完成)。其他用户发现他们需要10个,15个,50个甚至100个线程 - 因为他们有数万个具有各种时间表的触发器 - 最终平均有10到100个作业在任何给定时刻执行。为调度程序池找到合适的大小完全取决于您使用调度程序的内容。没有真正的规则,除了保持线程数尽可能小(为了您的机器的资源) - 但要确保您有足够的时间来发布您的工作。请注意,如果触发器的触发时间到来,并且没有可用的线程,Quartz将阻塞(暂停)直到线程可用,然后Job将执行 - 比它应该的晚几毫秒。如果在调度程序配置的“失火阈值”的持续时间内没有可用的线程,这甚至可能导致失败。然后Job将执行 - 比它应该的晚几毫秒。如果在调度程序配置的“失火阈值”的持续时间内没有可用的线程,这甚至可能导致失败。然后Job将执行 - 比它应该的晚几毫秒。如果在调度程序配置的“失火阈值”的持续时间内没有可用的线程,这甚至可能导致失败。

在Quartz.Spi命名空间中定义了IThreadPool接口,您可以以任何您喜欢的方式创建IThreadPool实现。Quartz附带一个名为Quartz.Simpl.SimpleThreadPool的简单(但非常令人满意)的线程池。这个IThreadPool实现只是在其池中维护一组固定的线程 - 永不增长,永不收缩。但它非常强大并且经过了很好的测试 - 几乎所有使用Quartz的人都使用这个池。

JobStores和DataSrouces在本教程的第9课中进行了讨论。值得注意的是,所有JobStores都实现了IJobStore接口 - 如果其中一个捆绑的JobStore不能满足您的需求,那么您可以创建自己的。

最后,您需要创建Scheduler实例。需要为Scheduler本身指定一个名称并传递JobStore和ThreadPool的实例。

StdSchedulerFactory

StdSchedulerFactory是ISchedulerFactory接口的实现。它使用一组属性(NameValueCollection)来创建和初始化Quartz Scheduler。这些属性通常存储在文件中并从文件中加载,但也可以由程序创建并直接传递给工厂。只需在工厂调用getScheduler()就可以生成调度程序,初始化它(及其ThreadPool,JobStore和DataSources),并返回其公共接口的句柄。

Quartz发行版的“docs / config”目录中有一些示例配置(包括属性的描述)。您可以在Quartz文档的“参考”部分下的“配置”手册中找到完整的文档。

DirectSchedulerFactory

DirectSchedulerFactory是另一个SchedulerFactory实现。对于那些希望以更加程序化的方式创建Scheduler实例的人来说,它非常有用。由于以下原因,通常不鼓励使用它:(1)它要求用户更好地理解他们正在做什么,以及(2)它不允许进行声明性配置 - 换句话说,你最终难以使用 - 编码所有调度程序的设置。

记录

Quartz.NET使用Common.Logging框架来满足其所有日志记录需求。Quartz不会产生太多的日志记录信息 - 通常只是在初始化期间的一些信息,然后只有在作业执行时有关严重问题的消息。为了“调整”日志记录设置(例如输出量以及输出的位置),您需要了解Commmon.Logging框架,这超出了本文档的范围,请参阅Common.Logging文档

第11课:高级(企业)功能

聚类

群集目前仅适用于AdoJobstore(JobStoreTX)。功能包括负载平衡和作业故障转移(如果JobDetail的“请求恢复”标志设置为true)。

通过将“quartz.jobStore.clustered”属性设置为“true”来启用群集。集群中的每个实例都应使用相同的quartz属性副本。例外情况是使用相同的属性,具有以下允许的例外:不同的线程池大小,以及“quartz.scheduler.instanceId”属性的不同值。集群中的每个节点必须具有唯一的instanceId,通过将“AUTO”作为此属性的值放置,可以轻松完成(不需要不同的属性文件)。

永远不要在不同的机器上运行群集,除非它们的时钟使用某种形式的时间同步服务(守护进程)进行同步,这些服务定期运行(时钟必须在彼此的秒内)。 如果您不熟悉如何执行此操作,访问http://www.boulder.nist.gov/timefreq/service/its.htm

切勿针对运行任何其他实例的同一组表启动非群集实例。您可能会遇到严重的数据损坏,并且肯定会遇到不稳定的行为。

第12课:Quartz的其他功能

插件

Quartz提供了一个接口(ISchedulerPlugin),用于插入附加功能。

Quartz.Plugins命名空间中可以找到Quartz附带的插件,以提供各种实用功能。它们提供的功能包括在调度程序启动时自动调度作业,记录作业历史记录和触发事件,以及确保调度程序在虚拟机退出时干净地关闭。

的JobFactory

触发器触发时,与其关联的作业将通过Scheduler上配置的JobFactory实例化。默认的JobFactory只是激活作业类的新实例。您可能希望创建自己的JobFactory实现来完成诸如让应用程序的IoC或DI容器生成/初始化作业实例之类的事情。

请参阅IJobFactory接口和相关的Scheduler.SetJobFactory(fact)方法。

'工厂出货'工作

Quartz还提供了许多实用工具,您可以在应用程序中使用它们来执行诸如发送电子邮件和调用远程对象之类的操作。可以在Quartz.Jobs命名空间中找到这些开箱即用的作业。

原文地址:https://www.cnblogs.com/xubao/p/11413026.html