Skip to content

Quartz.NET

🏷️ Quartz .NET Core

Quartz.NET 是一个定时计划任务的框架,支持 .NET Core。

本文示例代码大部分来自于官方教程:Quartz.NET - Quartz.NET 3.x Tutorial

Target Framework:.NET Core 2.2
Quartz:3.0.7

使用前首先要使用 NuGet 安装 Quartz.NET。

batch
Install-Package Quartz

标准的定时任务写法

csharp
 // construct a scheduler factory
NameValueCollection props = new NameValueCollection
{
    { "quartz.serializer.type", "binary" }
};
StdSchedulerFactory factory = new StdSchedulerFactory(props);

// get a scheduler
IScheduler sched = await factory.GetScheduler();
await sched.Start();

// 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();

await sched.ScheduleJob(job, trigger);

SchedulerFactory

StdSchedulerFactory

StdSchedulerFactory 继承 ISchedulerFactory 接口。

调用无参构造函数时自动加载根目录的 quartz.config 配置文件;也可以调用带有 NameValueCollection 参数的构造函数通过代码的方式配置。
实例化 StdSchedulerFactory 后调用 getScheduler() 方法生产 Scheduler,初始化 线程池、JobStore、数据源等。

DirectSchedulerFactory

需要使用更多自定义的 Scheduler 时可以使用 DirectSchedulerFactory ,该类同样继承 ISchedulerFactory 接口。
官方文档不建议在不了解时使用该 Factory。

IJob

Job

具体的 Job 继承 IJob 接口,这个接口只有一个 Execute 方法。

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

HelloJob.cs

csharp
class HelloJob : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        await Console.Out.WriteLineAsync("Greetings from HelloJob!");
    }
}

可通过 WithIdentity() 扩展方法指定 Job 的名称(name)和组(group),这两个值组成了这个 Job 的 Key({group}.{name}),这个 key 在整个 Scheduler 必须是唯一的。
这些信息储存在 context.JobDetail 中。

JobDataMap

可通过 UsingJobData 扩展方法 JobDataMap 的值。

csharp
// 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();

可通过 context.JobDetail.JobDataMap 获取。

csharp
JobDataMap dataMap = context.JobDetail.JobDataMap;

string jobSays = dataMap.GetString("jobSays");
float myFloatValue = dataMap.GetFloat("myFloatValue");

JobDataMap 中值的获取支持注入:

csharp
public class DumbJob : IJob
{
	public string JobSays { private get; set; }
	public float MyFloatValue { 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: " + MyFloatValue);
	}
}

Job 的状态和并发

  • DisallowConcurrentExecution 特性

    在 Job class 上添加该特性可以禁止同一个 JobDetail 中同一时间运行多个 Job 实例(但可以定义多个 JobDetail 使用同一个 Job class);

  • PersistJobDataAfterExecution 特性

    在 Job class 上添加该特性可以保留上一次运行结束后的 JobDataMap 到下一次运行。考虑到并发运行的可能最好和 DisallowConcurrentExecution 一起使用。
    Job class 属性使用注入时,修改属性的值并不会自动修改 JobDataMap 中的值,需要 JobDataMap.Put 方法更新其中的值。

    csharp
    context.JobDetail.JobDataMap.Put("count", Count);

Job 的其它属性

  • Durability :是否持久化;为 false 时如果没有任意一个激活的 Trigger,则将其从 Scheduler 中删除;默认值为 false;
  • RequestsRecovery :进程意外结束(如 PC 意外关机)时,被中断的任务是否重新执行;为 true 时会再次运行;默认值为 false;

ITrigger

ITrigger 的 Schedule 扩展方法:

  • WithCalendarIntervalSchedule :以指定的时间间隔(年、月、周、日、时、分、秒、毫秒)重复执行;
  • WithCronSchedule :使用 Cron 表达式指定何时执行;
  • WithSimpleSchedule :以指定时间间隔重复执行指定的次数或永久执行;
  • WithDailyTimeIntervalSchedule :可指定每天在何时开始运行、何时停止运行、每天运行多少次;

属性

  • JobKey :{group}.{name}
  • StartTimeUtc
  • EndTimeUtc
  • Priority:优先级(默认值为 5)

Misfire Instructions 计划失败时的指令

在 Schedule 中通过 WithMisfireHandlingInstructionFireNow() 等方法指定。默认使用 smart policy。

By default they use a ‘smart policy’ instruction - which has dynamic behavior based on trigger type and configuration.

csharp
ITrigger myTrigger = TriggerBuilder.Create()
    .WithIdentity("myTrigger", "group1")
    .StartNow()
    .WithSimpleSchedule(x => x
        .WithIntervalInSeconds(10)
        .RepeatForever()
        .WithMisfireHandlingInstructionFireNow()
        )
    .Build();

可通过 myTrigger.MisfireInstruction 属性获取该设置,默认值为 MisfireInstruction.InstructionNotSet

csharp
namespace Quartz
{
    //
    // 摘要:
    //     Misfire instructions.
    public struct MisfireInstruction
    {
        //
        // 摘要:
        //     Instruction not set (yet).
        public const int InstructionNotSet = 0;
        //
        // 摘要:
        //     Use smart policy.
        public const int SmartPolicy = 0;
        //
        // 摘要:
        //     Instructs the Quartz.IScheduler that the Quartz.ITrigger will never be evaluated
        //     for a misfire situation, and that the scheduler will simply try to fire it as
        //     soon as it can, and then update the Trigger as if it had fired at the proper
        //     time.
        //
        // 备注:
        //     NOTE: if a trigger uses this instruction, and it has missed several of its scheduled
        //     firings, then several rapid firings may occur as the trigger attempt to catch
        //     back up to where it would have been. For example, a SimpleTrigger that fires
        //     every 15 seconds which has misfired for 5 minutes will fire 20 times once it
        //     gets the chance to fire.
        public const int IgnoreMisfirePolicy = -1;

        //
        // 摘要:
        //     Misfire policy settings for SimpleTrigger.
        public struct SimpleTrigger
        {
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
            //     wants to be fired now by Quartz.IScheduler.
            //     NOTE: This instruction should typically only be used for 'one-shot' (non-repeating)
            //     Triggers. If it is used on a trigger with a repeat count > 0 then it is equivalent
            //     to the instruction Quartz.MisfireInstruction.SimpleTrigger.RescheduleNowWithRemainingRepeatCount.
            public const int FireNow = 1;
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
            //     wants to be re-scheduled to 'now' (even if the associated Quartz.ICalendar excludes
            //     'now') with the repeat count left as-is. This does obey the Quartz.ITrigger end-time
            //     however, so if 'now' is after the end-time the Quartz.ITrigger will not fire
            //     again.
            //
            // 备注:
            //     NOTE: Use of this instruction causes the trigger to 'forget' the start-time and
            //     repeat-count that it was originally setup with (this is only an issue if you
            //     for some reason wanted to be able to tell what the original values were at some
            //     later time).
            public const int RescheduleNowWithExistingRepeatCount = 2;
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
            //     wants to be re-scheduled to 'now' (even if the associated Quartz.ICalendar excludes
            //     'now') with the repeat count set to what it would be, if it had not missed any
            //     firings. This does obey the Quartz.ITrigger end-time however, so if 'now' is
            //     after the end-time the Quartz.ITrigger will not fire again.
            //     NOTE: Use of this instruction causes the trigger to 'forget' the start-time and
            //     repeat-count that it was originally setup with. Instead, the repeat count on
            //     the trigger will be changed to whatever the remaining repeat count is (this is
            //     only an issue if you for some reason wanted to be able to tell what the original
            //     values were at some later time).
            //     NOTE: This instruction could cause the Quartz.ITrigger to go to the 'COMPLETE'
            //     state after firing 'now', if all the repeat-fire-times where missed.
            public const int RescheduleNowWithRemainingRepeatCount = 3;
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
            //     wants to be re-scheduled to the next scheduled time after 'now' - taking into
            //     account any associated Quartz.ICalendar, and with the repeat count set to what
            //     it would be, if it had not missed any firings.
            //
            // 备注:
            //     NOTE/WARNING: This instruction could cause the Quartz.ITrigger to go directly
            //     to the 'COMPLETE' state if all fire-times where missed.
            public const int RescheduleNextWithRemainingCount = 4;
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ISimpleTrigger
            //     wants to be re-scheduled to the next scheduled time after 'now' - taking into
            //     account any associated Quartz.ICalendar, and with the repeat count left unchanged.
            //
            // 备注:
            //     NOTE/WARNING: This instruction could cause the Quartz.ITrigger to go directly
            //     to the 'COMPLETE' state if all the end-time of the trigger has arrived.
            public const int RescheduleNextWithExistingCount = 5;
        }
        //
        // 摘要:
        //     misfire instructions for CronTrigger
        public struct CronTrigger
        {
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ICronTrigger
            //     wants to be fired now by Quartz.IScheduler.
            public const int FireOnceNow = 1;
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ICronTrigger
            //     wants to have it's next-fire-time updated to the next time in the schedule after
            //     the current time (taking into account any associated Quartz.ICalendar), but it
            //     does not want to be fired now.
            public const int DoNothing = 2;
        }
        //
        // 摘要:
        //     Misfire instructions for DateIntervalTrigger
        public struct CalendarIntervalTrigger
        {
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ICalendarIntervalTrigger
            //     wants to be fired now by Quartz.IScheduler.
            public const int FireOnceNow = 1;
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.ICalendarIntervalTrigger
            //     wants to have it's next-fire-time updated to the next time in the schedule after
            //     the current time (taking into account any associated Quartz.ICalendar), but it
            //     does not want to be fired now.
            public const int DoNothing = 2;
        }
        //
        // 摘要:
        //     Misfire instructions for DailyTimeIntervalTrigger
        public struct DailyTimeIntervalTrigger
        {
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.IDailyTimeIntervalTrigger
            //     wants to be fired now by Quartz.IScheduler.
            public const int FireOnceNow = 1;
            //
            // 摘要:
            //     Instructs the Quartz.IScheduler that upon a mis-fire situation, the Quartz.MisfireInstruction.DailyTimeIntervalTrigger
            //     wants to have it's next-fire-time updated to the next time in the schedule after
            //     the current time (taking into account any associated Quartz.ICalendar), but it
            //     does not want to be fired now.
            public const int DoNothing = 2;
        }
    }
}

Calendars 日历

可以通过指定日历来排除执行的日期(如假日不执行)。
同一个日历可以用于多个 Trigger。

csharp
HolidayCalendar cal = new HolidayCalendar();
cal.AddExcludedDate(someDate);

await 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

自带的 Calendar 实现

位于 Quartz.Impl.Calendar 命名空间下。

  • AnnualCalendar:排除每年的某一天;
  • BaseCalendar:基础实现;
  • CronCalendar:基于 Cron 表达式排除日期;
  • DailyCalendar:排除每天的某个时间段;
  • HolidayCalendar:排除固定的某天;
  • MonthlyCalendar:排除每月的某天;
  • WeeklyCalendar:排除每周的某天;

自定义 Calendar

自定义 Calendar 时需实现 ICalendar 接口。

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

		ICalendar CalendarBase { set; get; }

		bool IsTimeIncluded(DateTimeOffset timeUtc);

		DateTime GetNextIncludedTimeUtc(DateTimeOffset timeUtc);

		ICalendar Clone();
	}
}

SimpleTrigger

几种常用的示例

  1. 仅在特定的时间执行一次

    csharp
    // trigger builder creates simple trigger by default, actually an ITrigger is returned
    ISimpleTrigger trigger = (ISimpleTrigger) TriggerBuilder.Create()
        .WithIdentity("trigger1", "group1")
        .StartAt(myStartTime) // some Date 
        .ForJob("job1", "group1") // identify job with name, group strings
        .Build();
  2. 从指定的时间开始,每隔 10 秒执行一次,共执行 10 次

    csharp
    trigger = TriggerBuilder.Create()
        .WithIdentity("trigger3", "group1")
        .StartAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
        .WithSimpleSchedule(x => x
            .WithIntervalInSeconds(10)
            .WithRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
        .ForJob(myJob) // identify job with handle to its JobDetail itself                   
        .Build();
  3. 五分钟后执行一次

    csharp
    trigger = (ISimpleTrigger) TriggerBuilder.Create()
        .WithIdentity("trigger5", "group1")
        .StartAt(DateBuilder.FutureDate(5, IntervalUnit.Minute)) // use DateBuilder to create a date in the future
        .ForJob(myJobKey) // identify job with its JobKey
        .Build();
  4. 从现在开始每 5 分钟执行一次,直到 22:00

    csharp
    trigger = TriggerBuilder.Create()
        .WithIdentity("trigger7", "group1")
        .WithSimpleSchedule(x => x
            .WithIntervalInMinutes(5)
            .RepeatForever())
        .EndAt(DateBuilder.DateOf(22, 0, 0))
        .Build();
  5. 下一个偶数的整点开始执行,之后每隔 2 小时执行一次

    csharp
    trigger = TriggerBuilder.Create()
        .WithIdentity("trigger8") // because group is not specified, "trigger8" will be in the default group
        .StartAt(DateBuilder.EvenHourDate(null)) // get the next even-hour (minutes and seconds zero ("00:00"))
        .WithSimpleSchedule(x => x
            .WithIntervalInHours(2)
            .RepeatForever())
        // note that in this example, 'forJob(..)' is not called 
        //  - which is valid if the trigger is passed to the scheduler along with the job  
        .Build();

SimpleTrigger Misfire Instructions

对于 SimpleTrigger 有以下几种 Misfire Instructions,每个都要一个对应的 SimpleScheduleBuilder 方法。

  • MisfireInstruction.IgnoreMisfirePolicy
  • MisfirePolicy.SimpleTrigger.FireNow
  • MisfirePolicy.SimpleTrigger.RescheduleNowWithExistingRepeatCount
  • MisfirePolicy.SimpleTrigger.RescheduleNowWithRemainingRepeatCount
  • MisfirePolicy.SimpleTrigger.RescheduleNextWithRemainingCount
  • MisfirePolicy.SimpleTrigger.RescheduleNextWithExistingCount
csharp
trigger = TriggerBuilder.Create()
    .WithIdentity("trigger7", "group1")
    .WithSimpleSchedule(x => x
        .WithIntervalInMinutes(5)
        .RepeatForever()
        .WithMisfireHandlingInstructionNextWithExistingCount())
    .Build();
csharp
public SimpleScheduleBuilder WithMisfireHandlingInstructionFireNow();
public SimpleScheduleBuilder WithMisfireHandlingInstructionIgnoreMisfires();
public SimpleScheduleBuilder WithMisfireHandlingInstructionNextWithExistingCount();
public SimpleScheduleBuilder WithMisfireHandlingInstructionNextWithRemainingCount();
public SimpleScheduleBuilder WithMisfireHandlingInstructionNowWithExistingCount();
public SimpleScheduleBuilder WithMisfireHandlingInstructionNowWithRemainingCount();

CronTrigger

可以通过 ITrigger 的 WithCronSchedule 扩展方法创建 CronTrigger,或者使用 WithSchedule 扩展方法 + CronScheduleBuilder 实现同样的效果。

下面的两个示例均表示在每周三的 10 点 42 分(美国时区)执行。

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

Cron Expressions(Cron 表达式)

Cron 表达式有 7 个组成部分,每个部分之间以空格分隔。

详细说明 => Jenkins – Poll SCM – 日程表 中的表达式写法 中的 Cron 表达式 部分。

CronTrigger Misfire Instructions

  • MisfireInstruction.IgnoreMisfirePolicy
  • MisfireInstruction.CronTrigger.DoNothing
  • MisfireInstruction.CronTrigger.FireOnceNow
csharp
trigger = TriggerBuilder.Create()
    .WithIdentity("trigger3", "group1")
    .WithCronSchedule("0 0/2 8-17 * * ?", x => x
        .WithMisfireHandlingInstructionFireAndProceed())
    .ForJob("myJob", "group1")
    .Build();
csharp
public CronScheduleBuilder WithMisfireHandlingInstructionDoNothing();
public CronScheduleBuilder WithMisfireHandlingInstructionFireAndProceed();
public CronScheduleBuilder WithMisfireHandlingInstructionIgnoreMisfires();

TriggerListeners and JobListeners

监听 Trigger 和 Job 中的事件。

TriggerListeners

TriggerListener 可以监听 Trigger 执行(TriggerFired、VetoJobExecution)、Trigger 未执行(TriggerMisfired)、Trigger 完成(TriggerComplete);
TriggerFired 方法最先执行,结束后调用 VetoJobExecution 方法。
如果 VetoJobExecution 方法返回 true,则 Job 不会被执行,并且不会触发 Trigger 完成事件。

ITriggerListener

csharp
//
// 摘要:
//     The interface to be implemented by classes that want to be informed when a Quartz.ITrigger
//     fires. In general, applications that use a Quartz.IScheduler will not have use
//     for this mechanism.
public interface ITriggerListener
{
    //
    // 摘要:
    //     Get the name of the Quartz.ITriggerListener.
    string Name { get; }

    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.ITrigger has fired, it's associated
    //     Quartz.IJobDetail has been executed, and it's Quartz.Spi.IOperableTrigger.Triggered(Quartz.ICalendar)
    //     method has been called.
    //
    // 参数:
    //   trigger:
    //     The Quartz.ITrigger that was fired.
    //
    //   context:
    //     The Quartz.IJobExecutionContext that was passed to the Quartz.IJob'sQuartz.IJob.Execute(Quartz.IJobExecutionContext)
    //     method.
    //
    //   triggerInstructionCode:
    //     The result of the call on the Quartz.ITrigger'sQuartz.Spi.IOperableTrigger.Triggered(Quartz.ICalendar)
    //     method.
    //
    //   cancellationToken:
    //     The cancellation instruction.
    Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.ITrigger has fired, and it's associated
    //     Quartz.IJobDetail is about to be executed.
    //     It is called before the Quartz.ITriggerListener.VetoJobExecution(Quartz.ITrigger,Quartz.IJobExecutionContext,System.Threading.CancellationToken)
    //     method of this interface.
    //
    // 参数:
    //   trigger:
    //     The Quartz.ITrigger that has fired.
    //
    //   context:
    //     The Quartz.IJobExecutionContext that will be passed to the Quartz.IJob'sQuartz.IJob.Execute(Quartz.IJobExecutionContext)
    //     method.
    //
    //   cancellationToken:
    //     The cancellation instruction.
    Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.ITrigger has misfired.
    //     Consideration should be given to how much time is spent in this method, as it
    //     will affect all triggers that are misfiring. If you have lots of triggers misfiring
    //     at once, it could be an issue it this method does a lot.
    //
    // 参数:
    //   trigger:
    //     The Quartz.ITrigger that has misfired.
    //
    //   cancellationToken:
    //     The cancellation instruction.
    Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.ITrigger has fired, and it's associated
    //     Quartz.IJobDetail is about to be executed.
    //     It is called after the Quartz.ITriggerListener.TriggerFired(Quartz.ITrigger,Quartz.IJobExecutionContext,System.Threading.CancellationToken)
    //     method of this interface. If the implementation vetoes the execution (via returning
    //     true), the job's execute method will not be called.
    //
    // 参数:
    //   trigger:
    //     The Quartz.ITrigger that has fired.
    //
    //   context:
    //     The Quartz.IJobExecutionContext that will be passed to the Quartz.IJob'sQuartz.IJob.Execute(Quartz.IJobExecutionContext)
    //     method.
    //
    //   cancellationToken:
    //     The cancellation instruction.
    //
    // 返回结果:
    //     Returns true if job execution should be vetoed, false otherwise.
    Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken));
}

MyTriggerListener.cs

csharp
class MyTriggerListener : ITriggerListener
{
    public string Name { get; set; }

    public async Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default(CancellationToken))
    {
        await Console.Out.WriteLineAsync($"MyTriggerListener({Name}.{trigger.Key}).TriggerComplete was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
    }

    public async Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
    {
        await Console.Out.WriteLineAsync($"MyTriggerListener({Name}.{trigger.Key}).TriggerFired was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
    }

    public async Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken))
    {
        await Console.Out.WriteLineAsync($"MyTriggerListener({Name}.{trigger.Key}).TriggerMisfired was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
    }

    public async Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
    {
        await Console.Out.WriteLineAsync($"MyTriggerListener({Name}.{trigger.Key}).VetoJobExecution was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
        return false;
    }
}

JobListeners

JobListener 可以监听 Job 即将被执行(JobToBeExecuted)、Job 执行结束(JobWasExecuted);
JobExecutionVetoed 方法则会在 TriggerListener.VetoJobExecution 返回值为 true 时执行。

IJobListener

csharp
//
// 摘要:
//     The interface to be implemented by classes that want to be informed when a Quartz.IJobDetail
//     executes. In general, applications that use a Quartz.IScheduler will not have
//     use for this mechanism.
public interface IJobListener
{
    //
    // 摘要:
    //     Get the name of the Quartz.IJobListener.
    string Name { get; }

    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail was about to be executed
    //     (an associated Quartz.ITrigger has occurred), but a Quartz.ITriggerListener vetoed
    //     it's execution.
    Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail is about to be executed
    //     (an associated Quartz.ITrigger has occurred).
    //     This method will not be invoked if the execution of the Job was vetoed by a Quartz.ITriggerListener.
    Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler after a Quartz.IJobDetail has been executed,
    //     and be for the associated Quartz.Spi.IOperableTrigger's Quartz.Spi.IOperableTrigger.Triggered(Quartz.ICalendar)
    //     method has been called.
    Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken));
}

MyJobListener.cs

csharp
class MyJobListener : IJobListener
{
    public string Name { get; set; }

    public async Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
    {
        await Console.Out.WriteLineAsync($"MyJobListener({Name}.{context.JobDetail.Key}).JobExecutionVetoed was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
    }

    public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken))
    {
        await Console.Out.WriteLineAsync($"MyJobListener({Name}.{context.JobDetail.Key}).JobToBeExecuted was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
    }

    public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken))
    {
        await Console.Out.WriteLineAsync($"MyJobListener({Name}.{context.JobDetail.Key}).JobWasExecuted was executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}.");
    }
}

使用 Listener

监听某个特定的 Job

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

监听某个特定组的 Job

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

监听两个特定组的 Job

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

监听所有的 Job

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

TriggerListener 用法类似。

监听所有的 Trigger

csharp
scheduler.ListenerManager.AddTriggerListener(myTriggerListener, GroupMatcher<TriggerKey>.AnyGroup());

运行结果

从下面打印的结果可以看出 TriggerListener 和 JobListener 中方法的调用顺序。

TriggerListener.TriggerFired => TriggerListener.VetoJobExecution => JobListener.JobToBeExecuted => Job 执行 => JobListener.JobWasExecuted => TriggerListener.TriggerComplete

csharp
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerFired was executed at 2019-02-12 13:55:29.758.
MyTriggerListener(trigger-listener-1.group1.trigger1).VetoJobExecution was executed at 2019-02-12 13:55:29.759.
MyJobListener(job-listener-1.group1.job1).JobToBeExecuted was executed at 2019-02-12 13:55:29.764.
Greetings from HelloJob(group1.job1 - 636855477297106001)!
MyJobListener(job-listener-1.group1.job1).JobWasExecuted was executed at 2019-02-12 13:55:29.770.
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerComplete was executed at 2019-02-12 13:55:29.776.

如果将 MyTriggerListener.VetoJobExecution 方法的返回值改为 true,则会执行 MyJobListener.JobExecutionVetoed 方法,并且 Job 不会被执行。

TriggerListener.TriggerFired => TriggerListener.VetoJobExecution => JobListener.JobExecutionVetoed

输出结果如下:

csharp
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerFired was executed at 2019-02-12 14:03:48.986.
MyTriggerListener(trigger-listener-1.group1.trigger1).VetoJobExecution was executed at 2019-02-12 14:03:48.987.
MyJobListener(job-listener-1.group1.job1).JobExecutionVetoed was executed at 2019-02-12 14:03:48.991.

SchedulerListeners

效果和用法同上面的两个 Listener 类似,只是监听的事件不一样。
SchedulerListener 监听的是 Scheduler 关联的事件,如 添加/删除 Trigger 或 Job、Scheduler 的异常、Scheduler 的关闭等。

ISchedulerListener

csharp
//
// 摘要:
//     The interface to be implemented by classes that want to be informed of major
//     Quartz.IScheduler events.
public interface ISchedulerListener
{
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail has been added.
    Task JobAdded(IJobDetail jobDetail, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail has been deleted.
    Task JobDeleted(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail has been interrupted.
    Task JobInterrupted(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail has been paused.
    Task JobPaused(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail has been un-paused.
    Task JobResumed(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail is scheduled.
    Task JobScheduled(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a group of Quartz.IJobDetails has been paused.
    //     If all groups were paused, then the jobName parameter will be null. If all jobs
    //     were paused, then both parameters will be null.
    //
    // 参数:
    //   jobGroup:
    //     The job group.
    //
    //   cancellationToken:
    //     The cancellation instruction.
    Task JobsPaused(string jobGroup, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail has been un-paused.
    //
    // 参数:
    //   jobGroup:
    //     The job group.
    //
    //   cancellationToken:
    //     The cancellation instruction.
    Task JobsResumed(string jobGroup, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.IJobDetail is unscheduled.
    Task JobUnscheduled(TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a serious error has occurred within the
    //     scheduler - such as repeated failures in the Quartz.Spi.IJobStore, or the inability
    //     to instantiate a Quartz.IJob instance when its Quartz.ITrigger has fired.
    Task SchedulerError(string msg, SchedulerException cause, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler to inform the listener that it has move to standby
    //     mode.
    Task SchedulerInStandbyMode(CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler to inform the listener that it has Shutdown.
    Task SchedulerShutdown(CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler to inform the listener that it has begun the
    //     shutdown sequence.
    Task SchedulerShuttingdown(CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler to inform the listener that it has started.
    Task SchedulerStarted(CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler to inform the listener that it is starting.
    Task SchedulerStarting(CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler to inform the listener that all jobs, triggers
    //     and calendars were deleted.
    Task SchedulingDataCleared(CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.ITrigger has reached the condition
    //     in which it will never fire again.
    Task TriggerFinalized(ITrigger trigger, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler a Quartz.ITriggers has been paused.
    Task TriggerPaused(TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a Quartz.ITrigger has been un-paused.
    Task TriggerResumed(TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler a group of Quartz.ITriggers has been paused.
    //
    // 参数:
    //   triggerGroup:
    //     The trigger group.
    //
    //   cancellationToken:
    //     The cancellation instruction.
    //
    // 备注:
    //     If a all groups were paused, then the triggerName parameter will be null.
    Task TriggersPaused(string triggerGroup, CancellationToken cancellationToken = default(CancellationToken));
    //
    // 摘要:
    //     Called by the Quartz.IScheduler when a group of Quartz.ITriggers has been un-paused.
    //
    // 参数:
    //   triggerGroup:
    //     The trigger group.
    //
    //   cancellationToken:
    //     The cancellation instruction.
    //
    // 备注:
    //     If all groups were resumed, then the triggerName parameter will be null.
    Task TriggersResumed(string triggerGroup, CancellationToken cancellationToken = default(CancellationToken));
}

添加 SchedulerListener

csharp
scheduler.ListenerManager.AddSchedulerListener(mySchedListener);

删除 SchedulerListener

csharp
scheduler.ListenerManager.RemoveSchedulerListener(mySchedListener);

执行输出结果(这里仅有部分方法被调用到):

csharp
[14:31:37] [Info] Quartz scheduler 'QuartzScheduler' initialized
[14:31:37] [Info] Quartz scheduler version: 3.0.7.0
[14:31:37] [Info] Scheduler QuartzScheduler_$_NON_CLUSTERED started.
MySchedulerListener(group1.job1).JobAdded was executed at 2019-02-12 14:31:37.395.
MySchedulerListener(group1.trigger1).JobScheduled was executed at 2019-02-12 14:31:37.399.
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerFired was executed at 2019-02-12 14:31:37.453.
MyTriggerListener(trigger-listener-1.group1.trigger1).VetoJobExecution was executed at 2019-02-12 14:31:37.454.
MyJobListener(job-listener-1.group1.job1).JobToBeExecuted was executed at 2019-02-12 14:31:37.458.
Greetings from HelloJob(group1.job1 - 636855498974031303)!
MyJobListener(job-listener-1.group1.job1).JobWasExecuted was executed at 2019-02-12 14:31:37.464.
MyTriggerListener(trigger-listener-1.group1.trigger1).TriggerComplete was executed at 2019-02-12 14:31:37.471.
MySchedulerListener(group1.trigger1).TriggerFinalized was executed at 2019-02-12 14:31:37.472.
MySchedulerListener(group1.job1).JobDeleted was executed at 2019-02-12 14:31:37.478.
[14:32:37] [Info] Scheduler QuartzScheduler_$_NON_CLUSTERED shutting down.
[14:32:37] [Info] Scheduler QuartzScheduler_$_NON_CLUSTERED paused.
MySchedulerListener.SchedulerInStandbyMode was executed at 2019-02-12 14:32:37.417.
MySchedulerListener.SchedulerShuttingdown was executed at 2019-02-12 14:32:37.419.
MySchedulerListener.SchedulerShutdown was executed at 2019-02-12 14:32:37.424.
[14:32:37] [Info] Scheduler QuartzScheduler_$_NON_CLUSTERED Shutdown complete.

JobStores(Job 存储)

RAMJobStore

默认使用的是 RAMJobStore,Job 保存在内存中。进程关闭后所有 Job 信息都会丢失。

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

AdoJobStore

AdoJobStore 将 Job、Trigger 设置经由 ADO.NET 保存到数据库。相比 RAMJobStore 没有那么快,但是可以持久化。另外可以通过对 Table 添加索引来提高数据库的效率。

配置项

  1. quartz.jobStore.type

    现在 Quartz.Impl.AdoJobStore.JobStoreTX 是唯一的 AdoJobStore 实现。

    properties
    quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
  2. quartz.jobStore.driverDelegateType

    Quartz.Impl.AdoJobStore.StdAdoDelegate 是个抽象的基类,具体应该设置成对应数据库的代理,如 MySQL 时应设置为 Quartz.Impl.AdoJobStore.MySQLDelegate, Quartz

    properties
    quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz
  3. quartz.jobStore.tablePrefix

    定义 Quartz 数据库表名的前缀。当在同一个数据库保存多个 Quartz 实例的数据时使用。
    QRTZ_ 是官方提供的建表 SQL 文中的表前缀。

    properties
    quartz.jobStore.tablePrefix = QRTZ_
  4. quartz.jobStore.dataSource

    定义数据源的名称,值可以自定义。

    properties
    quartz.jobStore.dataSource = myDS
  5. quartz.dataSource.myDS

    配置上面定义的数据源 myDS 的类型和连接字符串。

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

    支持如下数据库 Provider:

    • SqlServer - SQL Server driver for .NET Framework 2.0
    • OracleODP - Oracle’s Oracle Driver
    • OracleODPManaged - Oracle’s managed driver for Oracle 11
    • MySql - MySQL Connector/.NET
    • SQLite - SQLite ADO.NET Provider
    • SQLite-Microsoft - Microsoft SQLite ADO.NET Provider
    • Firebird - Firebird ADO.NET Provider
    • Npgsql - PostgreSQL Npgsql

示例

这里使用的 SqlServer 数据库。

  1. 执行 tables_sqlServer.sql,创建数据库;

    其它的数据库结构参照 quartznet/database/tables/ 中的其它文件。

  2. 初始化 StdSchedulerFactory 配置 AdoJobStore;

    使用代码配置时,使用 StdSchedulerFactory(NameValueCollection props) 构造函数,示例如下:

    csharp
    // Grab the Scheduler instance from the Factory
    NameValueCollection props = new NameValueCollection
    {
        { "quartz.serializer.type", "binary" },
        { "quartz.scheduler.instanceName", "MyScheduler" },
        { "quartz.jobStore.type", "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz" },
        { "quartz.jobStore.driverDelegateType", "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz" },
        { "quartz.jobStore.tablePrefix", "QRTZ_" },
        { "quartz.jobStore.dataSource", "myDS" },
        { "quartz.dataSource.myDS.connectionString", "Data Source=LIUJIAJIA\\LJJ2008;Initial Catalog=Quartz;User ID=sa;Password=ABC123;" },
        { "quartz.dataSource.myDS.provider", "SqlServer" },
        { "quartz.threadPool.threadCount", "3" },
    };
    StdSchedulerFactory factory = new StdSchedulerFactory(props);
    IScheduler scheduler = await factory.GetScheduler();
    
    // and start it off
    await scheduler.Start();
    
    // define the job and tie it to our HelloJob class
    IJobDetail job = JobBuilder.Create<HelloJob>()
        .WithIdentity("job1", "group1")
        .UsingJobData("count", 2)
        .Build();
    
    // Trigger the job to run now, and then repeat every 10 seconds
    ITrigger trigger = TriggerBuilder.Create()
        .WithIdentity("trigger1", "group1")
        .StartNow()
        .WithSimpleSchedule(x => x
            .WithIntervalInSeconds(10)
            .RepeatForever()
            )
        .Build();
    
    // Tell quartz to schedule the job using our trigger
    await scheduler.ScheduleJob(job, trigger);
    
    // and last shut down the scheduler when you are ready to close your program
    await scheduler.Shutdown();

    使用 quartz.config 配置文件时使用 StdSchedulerFactory() 构造函数。
    注意:需要将该配置文件的 复制到输出目录 属性设置为 如果较新则复制始终复制
    示例如下:

    quartz.config

    properties
    quartz.serializer.type = binary
    quartz.scheduler.instanceName = MyScheduler
    quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
    quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz
    quartz.jobStore.tablePrefix = QRTZ_
    quartz.jobStore.dataSource = myDS
    quartz.dataSource.myDS.connectionString = Data Source=LIUJIAJIA\LJJ2008;Initial Catalog=Quartz;User ID=sa;Password=Aa123456;
    quartz.dataSource.myDS.provider = SqlServer
    quartz.threadPool.threadCount = 3
    csharp
    // 加载当前目录下的 quartz.config 配置文件,若找不到则加载默认配置
    StdSchedulerFactory factory = new StdSchedulerFactory();
    IScheduler scheduler = await factory.GetScheduler();
    
    // and start it off
    await scheduler.Start();
    
    // define some jobs/triggers
    
    // and last shut down the scheduler when you are ready to close your program
    await scheduler.Shutdown();

集群

集群时仅能使用 AdoJobstore (JobStoreTX). 包含 负载均衡 和 故障转移(仅在 JobDetailrequest recovery 设置为 true 时)功能。

通过将 quartz.jobStore.clustered 设置为 true 来启动集群功能。

集群中的每个实例应该使用相同的配置。
其中 quartz.threadPool.threadCountquartz.scheduler.instanceId 是例外。
quartz.threadPool.threadCount 可以不一致。
quartz.scheduler.instanceId 在集群中则必须是唯一的。可以通过将 quartz.scheduler.instanceId 属性设置为 AUTO 来实现。该属性默认值为 NON_CLUSTERED

注意:禁止在不同的机器上开启集群,除非机器之间使用某种时间同步服务(误差必须在 1 秒以内)。

注意:不要在不是集群的实例间使用同一组数据表,否则会导致数据污染等问题。

插件

Quartz 提供了 ISchedulerPlugin 接口来加载附加功能。

具体可以参考 Quartz.Plugins 命名空间中的文档。

Quartz.Plugins 不包含在 Quartz 包中,需要单独安装。

batch
Install-Package Quartz.Plugins

当前最新版是 3.0.7,提供了如下插件:

  • LoggingJobHistoryPlugin

    Logs a history of all job executions (and execution vetoes) via common logging.

  • LoggingTriggerHistoryPlugin

    Logs a history of all trigger firings via the Jakarta Commons-Logging framework.

  • ShutdownHookPlugin

    This plugin catches the event of the VM terminating (such as upon a CRTL-C) and tells the scheduler to Shutdown.

  • XMLSchedulingDataProcessorPlugin

    This plugin loads XML file(s) to add jobs and schedule them with triggers as the scheduler is initialized, and can optionally periodically scan the file for changes.

JobFactory

当 Trigger 被触发时,通过 Scheduler 中配置的 JobFactory 来实例化 Job class。默认的 JobFactory 只是简单的创建一个新的 Job class 实例。
如果需要在创建/初始化 Job 实例时实现额外的处理(如使用 IoC 或 DI)可以通过实现 IJobFactory 接口定义自己的 JobFactory 来实现。

通过 Scheduler.SetJobFactory(fact) 方法来设置自定义的 JobFactory。

Factory-Shipped’ Jobs

Quartz 提供了一下公用的 Job(如发送电子邮件或调用远程作业)。你可以在 Quartz.Jobs 命名空间下发现这些开箱即用的功能。

Quartz.Jobs 不包含在 Quartz 包中,需要单独安装。

batch
Install-Package Quartz.Jobs

当前最新版是 3.0.7,提供了如下 Job:

  • DirectoryScanJob

    Inspects a directory and compares whether any files' "last modified dates" have changed since the last time it was inspected.
    If one or more files have been updated (or created), the job invokes a "call-back" method on an identified Quartz.Job.IDirectoryScanListener that can be found in the Quartz.SchedulerContext.

  • FileScanJob

    Inspects a file and compares whether it's "last modified date" has changed since the last time it was inspected.
    If the file has been updated, the job invokes a "call-back" method on an identified Quartz.Job.IFileScanListener that can be found in the Quartz.SchedulerContext.

  • NativeJob

    Built in job for executing native executables in a separate process.

    csharp
    JobDetail job = new JobDetail("dumbJob", null, typeof(Quartz.Jobs.NativeJob));
    job.JobDataMap.Put(Quartz.Jobs.NativeJob.PropertyCommand, "echo \"hi\" >> foobar.txt");
    Trigger trigger = TriggerUtils.MakeSecondlyTrigger(5);
    trigger.Name = "dumbTrigger";
    sched.ScheduleJob(job, trigger);

    If PropertyWaitForProcess is true, then the integer exit value of the process will be saved as the job execution result in the JobExecutionContext.

  • NoOpJob

    An implementation of Job, that does absolutely nothing - useful for system which only wish to use Quartz.ITriggerListeners and Quartz.IJobListeners, rather than writing Jobs that perform work.

  • SendMailJob

    A Job which sends an e-mail with the configured content to the configured recipient.

quartz.serializer.type

这个配置项指定 Job/Trigger 中的数据保存到数据库时的序列化及反序列类型。

在使用非 RAMJobStore 时必须设置该配置,否则会报如下错误。可以选择的值为 binaryjson

csharp
Quartz.SchedulerException: You must define object serializer using configuration key 'quartz.serializer.type' when using other than RAMJobStore. Out of the box supported values are 'json' and 'binary'. JSON doesn't suffer from versioning as much as binary serialization but you cannot use it if you already have binary serialized data.
   at Quartz.Impl.StdSchedulerFactory.Instantiate() in C:\projects\quartznet\src\Quartz\Impl\StdSchedulerFactory.cs:line 352
   at Quartz.Impl.StdSchedulerFactory.GetScheduler(CancellationToken cancellationToken) in C:\projects\quartznet\src\Quartz\Impl\StdSchedulerFactory.cs:line 1114
   at FirstQuartzNet.Program.RunProgram() in C:\Users\liujiajia\source\repos\FirstQuartzNet\FirstQuartzNet\Program.cs:line 45

如果设置为 json 需要安装 Quartz.Serialization.Json 包。

batch
Install-Package Quartz.Serialization.Json