实用技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > ASP.NET > 实用技巧 > Quartz.NET全面介绍

Quartz.NET 全面解析与实战指南

作者:回忆2012初秋

文章介绍了Quartz.NET,一个功能强大的分布式作业调度框架,适用于.NET应用,它支持Cron表达式、异步执行、持久化存储和集群部署等特性,提供了最佳实践和注意事项,感兴趣的朋友一起看看吧

一、Quartz.NET 简介

Quartz.NET 是一个功能强大、开源的作业调度框架,源自 OpenSymphony 的 Quartz API 的 .NET 移植版本,用 C# 改写,适用于 Winform、ASP.NET 以及 ASP.NET Core 等各类 .NET 应用。它的核心能力可以概括为:在指定的时间或按照指定的周期性地执行任务——无论是每天早上 8 点发送一封汇总邮件、每隔 10 分钟轮询一次接口状态,还是在工作日的特定时段内执行数据同步。

Quartz.NET 的主要特性包括:

版本说明:Quartz.NET 3.0 是一个重大更新,引入了完整的 async/await 支持,并对 .NET Core 提供了原生支持,NuGet 包也进行了拆分,例如 Quartz.JobsQuartz.Plugins 等成为独立的依赖项。

二、核心概念与架构

核心组件

组件描述
Job(作业)实现 IJob 接口的类,包含具体要执行的业务逻辑
Trigger(触发器)定义任务何时执行的规则,如时间间隔、Cron 表达式等
Scheduler(调度器)核心引擎,负责管理和协调 Job 与 Trigger 的执行
JobStore(作业存储)存储作业和触发器信息,支持内存和数据库两种模式
ThreadPool(线程池)为任务执行提供线程资源

基本工作流程

  1. 定义 Job(实现 IJob 接口的类),编写业务逻辑
  2. 创建 Trigger,设定触发规则(时间间隔、Cron 表达式等)
  3. 通过 Scheduler 将 Job 和 Trigger 绑定并启动调度
  4. 调度器按照 Trigger 的规则,在适当的时机通过线程池执行 Job

3.x 的核心变化:全面异步化

从 3.0 版本开始,Quartz.NET 全面拥抱异步编程。IJobExecute 方法现在返回 Task,可以轻松包含 async 代码:

// 3.x 的 Job 定义
public class MyJob : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        // 异步操作示例
        await Task.Delay(1);
    }
}

如果你没有任何异步逻辑,也可以直接返回 Task.CompletedTask

三、快速安装与配置

1. 安装 NuGet 包

# 核心包
dotnet add package Quartz
# ASP.NET Core 托管集成
dotnet add package Quartz.Extensions.Hosting
# 如需持久化支持(例如 SQL Server)
dotnet add package Quartz.Plugins
dotnet add package Quartz.Serialization.Json

2. 定义 Job 类

using Quartz;
public class HelloJob : IJob
{
    private readonly ILogger<HelloJob> _logger;
    public HelloJob(ILogger<HelloJob> logger)
    {
        _logger = logger;
    }
    public Task Execute(IJobExecutionContext context)
    {
        _logger.LogInformation("Hello! 当前时间: {Time}", DateTime.Now);
        return Task.CompletedTask;
    }
}

Quartz.NET 通过 IJobExecutionContext 向作业传递上下文信息,其中包括 JobDataMap,可以在 Trigger 或 Scheduler 级别传递参数给 Job。

3. 注册 Quartz 到 DI 容器

Program.cs 中完成注册:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddQuartz(q =>
{
    // 注册 Job
    var jobKey = new JobKey("HelloJob");
    q.AddJob<HelloJob>(opts => opts.WithIdentity(jobKey));
    // 注册触发器 - SimpleSchedule 方式
    q.AddTrigger(opts => opts
        .ForJob(jobKey)
        .WithIdentity("HelloJob-trigger")
        .WithSimpleSchedule(x => x.WithIntervalInSeconds(10).RepeatForever()));
});
// 添加 Quartz 托管服务,随应用生命周期启停
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
var app = builder.Build();
app.Run();

四、触发器(Trigger)详解

Trigger 是 Quartz.NET 中最灵活的组件之一,定义了作业何时执行。

1. SimpleSchedule —— 简单间隔调度

适用于"每隔 N 秒/分钟/小时执行一次"这类固定间隔的场景:

q.AddTrigger(opts => opts
    .ForJob("HelloJob")
    .WithIdentity("simple-trigger")
    .WithSimpleSchedule(x => x
        .WithIntervalInMinutes(30)  // 每 30 分钟执行
        .RepeatForever()));         // 无限重复

还可以控制重复次数、开始时间和结束时间:x.WithRepeatCount(10) 表示总共执行 10 次后停止。

2. CronSchedule —— Cron 表达式调度

当需要日历化的复杂调度规则时(如"每个工作日早上 9 点"),使用 Cron 表达式:

q.AddTrigger(opts => opts
    .ForJob("HelloJob")
    .WithIdentity("cron-trigger")
    .WithCronSchedule("0 0 9 ? * MON-FRI")); // 工作日早上9点

3. Cron 表达式详解

Quartz.NET 使用 7 字段(年可选)的 Cron 表达式:

字段是否必填允许值允许的特殊字符
0-59, - * /
0-59, - * /
小时0-23, - * /
日期1-31, - * ? / L W
月份1-12 或 JAN-DEC, - * /
星期1-7 或 SUN-SAT, - * ? / L #
空或 1970-2099, - * /

例如 0 0 12 ? * WED 表示"每周三中午 12:00 执行"。

常用特殊字符

字符含义示例
*所有值* * * * * ? 表示每秒
?不指定值(日期和星期互斥)日期字段为 ? 时不关心具体日期
-范围MON-FRI 表示周一至周五
,列举MON,WED,FRI 表示周一、三、五
/增量0/15 在分钟字段表示每 15 分钟,从第 0 分钟开始
L最后L 在日期字段表示当月最后一天
#第 N 个6#3 在星期字段表示第 3 个周五

常用表达式示例

0 0/5 * * * ?              # 每 5 分钟执行一次
0 0 2 * * ?                # 每天凌晨 2 点执行
0 15 10 ? * MON-FRI        # 周一至周五上午 10:15 执行
0 0 9 ? * 6L               # 每月最后一个周五上午 9 点
0 0/30 9-17 ? * MON-FRI    # 工作日 9:00-17:00 内每 30 分钟执行

4. 其他高级触发器

Quartz.NET 还支持 DailyTimeIntervalTrigger,可以方便地定义每日时间段内的调度:

var trigger = TriggerBuilder.Create()
    .WithIdentity("officeHoursTrigger")
    .WithSchedule(DailyTimeIntervalScheduleBuilder.Create()
        .OnMondayThroughFriday()
        .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(9, 0))
        .EndingDailyAt(TimeOfDay.HourAndMinuteOfDay(17, 0))
        .WithIntervalInHours(2))
    .Build();

五、依赖注入与 Job 工厂

Quartz.NET 天然支持 ASP.NET Core 的依赖注入系统。创建带依赖的 Job 非常简单:

// 定义有依赖注入的 Job
public class EmailJob : IJob
{
    private readonly IEmailService _emailService;
    public EmailJob(IEmailService emailService)
    {
        _emailService = emailService;
    }
    public async Task Execute(IJobExecutionContext context)
    {
        await _emailService.SendAsync("定时邮件", "来自 Quartz.NET 的问候");
    }
}
// 注册服务和 Job
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddQuartz(q =>
{
    q.UseMicrosoftDependencyInjectionJobFactory(); // 启用 DI 工厂
    q.AddJob<EmailJob>(opts => opts.WithIdentity("EmailJob"));
});

UseMicrosoftDependencyInjectionJobFactory() 告诉 Quartz 使用 Microsoft 的 DI 容器来创建 Job 实例,这样构造函数中注入的服务就能被正确解析。

六、任务持久化(JobStore)

Quartz.NET 提供两种存储模式:

1. RAMJobStore —— 内存存储

默认的存储模式,将所有数据保存在内存中,速度最快,但应用停止或崩溃后任务信息会丢失。适合测试环境或不在意任务丢失的轻量场景。

2. AdoJobStore —— 数据库持久化

将 Jobs 和 Triggers 存储到关系型数据库中,确保任务在应用重启后不会丢失,也是集群部署的基础。

配置步骤

(1)首先创建数据库表——官方提供了 SQL 建表脚本,位于 database/dbtables 目录下,所有表默认以 QRTZ_ 为前缀。

(2)配置连接字符串和 JobStore:

Quartz.NET 4.1 及以上版本支持使用 appsettings.json 进行层次化 JSON 配置,更加直观:

{
  "Quartz": {
    "Scheduler": {
      "InstanceName": "My Scheduler",
      "InstanceId": "AUTO"
    },
    "ThreadPool": {
      "MaxConcurrency": 10
    },
    "JobStore": {
      "Type": "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
      "DataSource": "default",
      "TablePrefix": "QRTZ_"
    },
    "DataSource": {
      "default": {
        "Provider": "SqlServer",
        "ConnectionString": "Server=localhost;Database=quartznet"
      }
    }
  }
}

(3)在代码中使用 JSON 配置:

builder.Services.AddQuartz(Configuration.GetSection("Quartz"), q =>
{
    // 代码配置可与 JSON 配置并存
});

七、监听器(Listeners)——AOP 式任务监控

监听器是 Quartz.NET 中实现任务监控和横切关注点(AOP)的关键机制。通过实现监听器接口,可以在调度生命周期中插入自定义逻辑(如日志记录、性能监控、告警通知等)。

1. JobListener —— 监听 Job 事件

接收与 Job 执行相关的三个事件:

public class LoggingJobListener : JobListenerSupport
{
    public override string Name => "LoggingJobListener";
    public override ValueTask JobToBeExecuted(IJobExecutionContext context)
    {
        Console.WriteLine($"Job [{context.JobDetail.Key}] 即将执行...");
        return ValueTask.CompletedTask;
    }
    public override ValueTask JobWasExecuted(IJobExecutionContext context,
        JobExecutionException? jobException)
    {
        if (jobException == null)
            Console.WriteLine($"Job [{context.JobDetail.Key}] 执行成功");
        else
            Console.WriteLine($"Job [{context.JobDetail.Key}] 执行失败: {jobException.Message}");
        return ValueTask.CompletedTask;
    }
}

2. TriggerListener —— 监听 Trigger 事件

接收与触发器相关的四个事件:

public class MetricsTriggerListener : TriggerListenerSupport
{
    public override string Name => "MetricsTriggerListener";
    public override ValueTask TriggerFired(ITrigger trigger, IJobExecutionContext context)
    {
        // 触发器触发时记录指标
        return ValueTask.CompletedTask;
    }
    public override ValueTask TriggerMisfired(ITrigger trigger)
    {
        // 处理错过触发的情况
        Console.WriteLine($"Trigger [{trigger.Key}] 错过触发!");
        return ValueTask.CompletedTask;
    }
}

> 关键提示

3. 注册监听器

监听器通过 ListenerManager 注册,配合 Matcher 精确控制作用范围:

scheduler.ListenerManager.AddJobListener(
    myJobListener,
    KeyMatcher<JobKey>.KeyEquals(new JobKey("myJobName", "myJobGroup")));
// 监听特定组的所有 Job
scheduler.ListenerManager.AddJobListener(
    myJobListener,
    GroupMatcher<JobKey>.GroupEquals("myJobGroup"));
// 监听所有 Job
scheduler.ListenerManager.AddJobListener(
    myJobListener,
    GroupMatcher<JobKey>.AnyGroup());

八、高级特性详解

1. 集群部署

Quartz.NET 支持多节点集群,实现负载均衡和故障转移。集群模式下,所有节点共享同一个数据库(通过 AdoJobStore),通过数据库锁机制确保同一个 Job 不会被多个节点同时执行。

配置要点

适用场景:集群特性最适用于将长时间运行的计算密集型任务分布到多个节点执行,实现负载分担。

2. 多调度器(Multiple Schedulers)

从 Quartz.NET 4.x 开始,可以通过 AddQuartz(string name, ...) 创建命名调度器,实现工作负载隔离:

// 快速的内存调度器(用于高频轻量任务)
builder.Services.AddQuartz("FastScheduler", q =>
{
    q.UseInMemoryStore();
    q.UseDefaultThreadPool(tp => tp.MaxConcurrency = 5);
    q.ScheduleJob<NotificationJob>(trigger => trigger
        .WithSimpleSchedule(x => x.WithIntervalInSeconds(30).RepeatForever()));
});
// 持久化的数据库调度器(用于关键业务任务)
builder.Services.AddQuartz("DurableScheduler", q =>
{
    q.UsePersistentStore(s =>
    {
        s.UseSqlServer(sql => sql.ConnectionString = "your connection string");
    });
    q.ScheduleJob<ReportJob>(trigger => trigger
        .WithCronSchedule("0 0 2 * * ?"));
});
// 单次调用启动所有命名调度器
builder.Services.AddQuartzHostedService(options =>
{
    options.WaitForJobsToComplete = true;
});

这种做法可以让不同的调度器使用不同的存储策略、线程池和配置,彼此完全隔离。

3. 动态作业管理

通过 IScheduler 接口可以在运行时动态管理任务:

// 动态添加作业
await scheduler.ScheduleJob(jobDetail, trigger);
// 暂停作业
await scheduler.PauseJob(jobKey);
// 恢复作业
await scheduler.ResumeJob(jobKey);
// 删除作业
await scheduler.DeleteJob(jobKey);
// 检查作业是否存在
bool exists = await scheduler.CheckExists(jobKey);

4. 传递参数 —— JobDataMap

JobDataMap 是 Quartz.NET 中向 Job 传递参数的机制,可以在 Trigger 定义时或 Scheduler 级别设置键值对:

// 在 Trigger 定义时传递参数(示例)
q.AddTrigger(opts => opts
    .ForJob("HelloJob")
    .UsingJobData("RetryCount", "3")
    .UsingJobData("TimeoutSeconds", "30")
    .WithCronSchedule("0 0/10 * * * ?"));

生产建议:CronTrigger 应显式设置时区,参数通过 JobDataMap 传递,任务依赖需手动触发或使用监听器实现。

九、Quartz.NET vs Hangfire:如何选型?

两个框架都能实现定时任务,但定位和适用场景有明显差异:

维度Quartz.NETHangfire
核心定位企业级调度引擎通用后台任务处理框架
调度能力复杂 Cron、日历调度、时间段约束基本 Cron 表达式
持久化可选(RAM / 数据库)默认依赖数据库持久化
可视化面板无(需第三方如 Quartzmin)内置 Dashboard
集群支持原生数据库锁机制基于持久化存储的任务分发
学习曲线较陡峭较平缓
执行精度毫秒级秒级
最佳场景复杂调度策略、金融系统、企业批处理可靠任务执行、可观测性要求高的场景

选型建议

十、最佳实践与注意事项

1. Job 设计原则

2. 配置建议

3. 调试与排查

4. 动态作业管理

十一、资源与扩展

到此这篇关于Quartz.NET 全面解析与实战指南的文章就介绍到这了,更多相关Quartz.NET全面介绍内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文