Quartz.NET是一个功能齐全的作业调度系统,可用于小型应用程序,也可用于大型企业系统。Quartz.NET是用C#编写的.NET库,移植于开源的Java作业调度框架Quartz。

Quartz简介

Quartz是一个作业调度系统,作业调度程序是一个负责在执行前执行(或者通知)其他软件组件的系统。

Quartz具有容错能力,可以在系统重启之间依然保持作业任务。

Quartz主要接口是Scheduler接口,它提供了简单的操作,如调度作业,启动/停止/暂停调度程序。

如果安排自己的软件组件执行,必须实现Job接口,执行Execute方法。

如果希望在触发时间前通知组件,必须实现TriggerListener或JobListener接口。

Quartz与Timer

.NET Framework具有内置计时器功能,System.Timers.Timer类,但与Quartz比较,不足之处:

  • 定时器没有持久性机制
  • 定时器灵活度低(仅能够设置开始时间和重复间隔)
  • 定时器不使用线程池(每个定时器一个线程)
  • 计时器没有真正的管理方案,需要自己编写机制

Quartz 3.X

Quartz使用

  1. 创建控制台应用程序,安装Quartz

    Install-Package Quartz

  2. 创建自定义任务HelloJob

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    namespace QuartzStart
    {
    public class HelloJob : IJob
    {
    public async Task Execute(IJobExecutionContext context)
    {
    await Console.Out.WriteLineAsync("Hello World");
    }
    }
    }
    //自定义任务HelloJOb,需要实现IJob接口,实现接口的唯一方法Excute
  3. 设置记录日志ConsoleLogProvider

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    namespace QuartzStart
    {
    public class ConsoleLogProvider : ILogProvider
    {
    public Logger GetLogger(string name)
    {
    return (level, func, exception, parameters) =>
    {
    if (level >= LogLevel.Info && func != null)
    {
    Console.WriteLine("[" + DateTime.Now.ToLongTimeString() + "] [" + level + "] " + func(), parameters);
    }
    return true;
    };
    }

    public IDisposable OpenMappedContext(string key, string value)
    {
    throw new NotImplementedException();
    }

    public IDisposable OpenNestedContext(string message)
    {
    throw new NotImplementedException();
    }
    }
    }
    //Quartz默认的日志框架是LibLog,当然可以设置其他日志框架,如Log4NET,NLog和Serilog
  4. 创建调度(Scheduler) 触发(Trigger)任务(JobDetail)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    namespace QuartzStart
    {
    class Program
    {
    public static void Main(string[] args)
    {
    //记录日志:使用默认日志框架LibLog
    LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());

    //异步求值
    RunProgram().GetAwaiter().GetResult();
    Console.Read();
    }

    private static async Task RunProgram()
    {
    try
    {
    NameValueCollection props = new NameValueCollection {
    {"quartz.serializer.type","binary"}
    };

    StdSchedulerFactory factory = new StdSchedulerFactory(props);

    //通过工厂获取Scheduler实例
    IScheduler scheduler = await factory.GetScheduler();

    //启动调度Scheduler
    await scheduler.Start();

    //通过JobBuilder创建任务JobDetail实例,并与自定义任务HelloJob关连
    IJobDetail job = JobBuilder.Create<HelloJob>().WithIdentity("job1", "group1").Build();

    //通过TriggerBulider创建触发器实例Trigger,并设置立即触发任务,且每10秒重复一次
    ITrigger trigger = TriggerBuilder.Create().WithIdentity("trigger1", "group1").StartNow()
    .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).RepeatForever()).Build();

    //通过Trigger执行调度任务Job
    await scheduler.ScheduleJob(job, trigger);

    //等待一分钟
    await Task.Delay(TimeSpan.FromSeconds(60));
    //关闭调度 当你关闭应用程序
    await scheduler.Shutdown();

    }
    catch (SchedulerException se)
    {
    Console.WriteLine(se);
    }
    }
    }
    }
  5. 运行结果

    QuartzStart.jpg

Job和Trigger

Quartz API关键的接口和类:

  • IScheduler:与调度程序交互的主要接口
  • IJob:调度程序执行的任务实现的接口
  • IJobDetail:定义任务实例接口
  • ITrigger:定义执行任务的触发器(计划)
  • JobBuilder:创建JobDetail实例
  • TriggerBuilder:创建触发器实例

Job和JobDetail

自定义的任务类型需要实现IJob接口,通过JobBuilder创建JobDetail实例。

JobBuilder每次触发都会创建新的实例,这就要求:

  • 自定义任务类具有一个无参的构造函数
  • 在任务类上定义数据字段没有意义,它们的值不会在作业之间保留

如果想为Job实例提供属性或者执行作业跟踪作业状态,就要用到JobDataMap。

JobDataMap可用于保存在作业实例执行时可用的任意数量的(可序列化)对象。JobDataMap是IDictionary接口的一个实现。注意:JobDataMap存入复杂对象,序列化可能出现类版本问题。

  • JobDataMap设置值

    1
    2
    3
    4
    5
    //为JobDetail的JobDataMap属性添加数据
    IJobDetail job = JobBuilder.Create<HelloJob>().WithIdentity("job1", "group1")
    .UsingJobData("jobSay", "HelloWorld")
    .UsingJobData("myAge", 18)
    .Build();
  • JobDataMap获取值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    namespace QuartzStart
    {
    public class HelloJob : IJob
    {
    public async Task Execute(IJobExecutionContext context)
    {
    JobKey key = context.JobDetail.Key;
    //读取JobDetail属性JobDataMap存储的值
    JobDataMap dataMap = context.JobDetail.JobDataMap;
    string jobSay = dataMap.GetString("jobSay");
    int myAge = dataMap.GetInt("myAge");

    await Console.Out.WriteLineAsync("Instance " + key + " of HelloJob says: " + jobSay + ", and age is: " + myAge);
    }
    }
    }

    //在作业类中添加与JobDataMap键名称相对应的属性,则Quartz在实现作业类实例化时会自动为属性赋值。
    namespace QuartzStart
    {
    public class HelloJob : IJob
    {
    //同名属性首字母大写
    public string JobSay { private get; set; }
    public int MyAge { private get; set; }
    public async Task Execute(IJobExecutionContext context)
    {
    JobKey key = context.JobDetail.Key;
    await Console.Out.WriteLineAsync("Instance " + key + " of HelloJob says: " + JobSay + ", and age is: " + MyAge);
    }
    }
    }s
  • 运行结果

    QuartzJobDataMap.jpg

Trigger

  • TriggerKey:触发器身份
  • Prioroty:触发器优先级
  • Calendars:实现ICalendar接口的类可以与触发器关连

SimpleTrigger

  • 特定时刻执行一次作业,如22点49分触发
  • 特定时刻执行作业,然后按特定时间间隔重复,如10秒触发一次
    • 开始时间:StartAt
    • 结束时间:EndAt
    • 重复计数:WithRepeatCount
    • 重复间隔:WithIntervalIn….
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//1.在当前时间触发一次
ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartAt(DateTime.UtcNow)
.ForJob("job1", "group1")
.Build();

//2.在当前时间触发,并且每10秒触发一次,重复10次,一共触发11次
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger2", "group1")
.StartNow()
.WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(10))
.ForJob("job1", "group1")
.Build();

//3.在未来一分钟内触发一次
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger3", "group1")
.StartAt(DateBuilder.FutureDate(1, IntervalUnit.Minute))
.ForJob("job1", "group1")
.Build();

//4.每5秒重复触发一次,直到23:25:00
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger4", "group1")
.WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever())
.EndAt(DateBuilder.DateOf(23, 25, 0))
.Build();

//5.从下一个小时的0分0秒开始触发,每2小时触发一次,直到永远
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger5", "group1")
.StartAt(DateBuilder.EvenHourDate(null))
.WithSimpleSchedule(x => x.WithIntervalInHours(2).RepeatForever())
.Build();

CronTrigger

基于日历设置作业计划,如每周五上午9:00

  • 开始时间
  • 结束时间
  • Corn表达式
    • Corn表达式由七个子表达式组成,用空格分隔
    • 秒 分 时 日 月 周 年,如0 0 12 ? * WED 表示“每周三中午12点”
1
2
3
4
5
6
 //每天23点到0点之间,每隔一分钟触发一次
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger5", "group1")
.WithCronSchedule("0 0/2 23-0 * * ?")
.ForJob("job1", "group1")
.Build();

TriggerListeners

接收与触发器相关的事件,可以创建自己的监听器,通过实现ITriggerListener接口,方便起见,可以扩展TriggerListenerSupport类,覆盖相应的事件

1
2
3
4
5
6
7
8
9
10
11
12
public interface ITriggerListener
{
string Name { get; }
//触发器触发
Task TriggerFired(ITrigger trigger, IJobExecutionContext context);
//触发器触发的作业完成
Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context);
//触发器错误触发
Task TriggerMisfired(ITrigger trigger);
//触发器完成
Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, int triggerInstructionCode);
}

JobListeners

接收与作业相关的事件,可以创建自己的监听器,通过实现IJobListener接口,方便起见,可以扩展JobListenerSupport类,覆盖相应的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IJobListener
{
string Name { get; }
//作业即将执行的通知
Task JobToBeExecuted(IJobExecutionContext context);

Task JobExecutionVetoed(IJobExecutionContext context);
//作业完成执行时通知
Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException);
}

//添加对所有作业的JobListener
scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.AnyGroup());

SchedulerListeners

接收调度程序相关的事件,SchedulerListeners在ListenerManager中注册。

  • 新增SchedulerListener:scheduler.ListenerManager.AddSchedulerListener(mySchedListener);
  • 删除SchedulerListener:scheduler.ListenerManager.RemoveSchedulerListener(mySchedListener);

JobStores

负责根据你为调度程序提供的所有工作数据:作业、触发器、日历等,切勿在代码中直接使用JobStore实例。

  • RAMJobStore

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

  • ADO.NET JobStore(ADOJobStore)

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

× 请我吃糖~
打赏二维码