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

 更新时间:2026年05月04日 08:39:29   作者:回忆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 的主要特性包括:

  • 支持 Cron 表达式,实现灵活的日历化调度
  • 持久化任务信息到数据库,避免应用重启后任务丢失
  • 分布式集群部署,支持负载均衡与故障转移
  • 动态管理任务,支持添加、暂停、恢复、删除作业
  • 深度集成 ASP.NET Core 依赖注入系统
  • 支持插件和监听器,可自定义调度逻辑和监控机制

版本说明: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;
    }
}

> 关键提示

  • 异常处理:确保监听器内不抛出未捕获异常,否则可能导致作业卡死
  • 不持久化:监听器注册在运行时,不会存入 JobStore,每次应用启动都需重新注册
  • 使用辅助基类:继承 JobListenerSupportTriggerListenerSupport 可以只重写感兴趣的方法

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 不会被多个节点同时执行。

配置要点

  • 所有共享同一套数据库和表结构
  • 设置 org.quartz.jobStore.isClustered = true
  • 各节点的线程池参数保持一致
  • 确保各节点时钟同步

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

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
集群支持原生数据库锁机制基于持久化存储的任务分发
学习曲线较陡峭较平缓
执行精度毫秒级秒级
最佳场景复杂调度策略、金融系统、企业批处理可靠任务执行、可观测性要求高的场景

选型建议

  • Hangfire 更适合快速集成、注重任务可观测性、需要内置 Dashboard 的场景——集成简单,自带监控面板
  • Quartz.NET 更适合需要精确、复杂调度策略的企业级系统——支持丰富 Cron 表达式、日历间隔等高级调度规则,在复杂调度场景下更为灵活

十、最佳实践与注意事项

1. Job 设计原则

  • 幂等性:Job 逻辑应设计为可安全重复执行的,避免集群或重试场景下产生重复数据。集群模式下,重复执行需正确配置并保障幂等性
  • 轻量化:Job 的 Execute 方法应尽快完成,耗时长的工作应拆分到子任务或消息队列中异步处理
  • 异常处理:在 Execute 方法中妥善处理异常,返回适当的 JobExecutionException 以控制重试行为
  • 避免线程阻塞:充分利用 async/await 避免阻塞调度线程

2. 配置建议

  • 生产环境必须使用 AdoJobStore,避免应用重启导致任务丢失
  • 合理设置线程池大小:MaxConcurrency 应根据任务特点和服务器资源调整,默认为 10
  • 配置 WaitForJobsToComplete:优雅关闭时等待正在执行的任务完成

3. 调试与排查

  • 利用 LoggingJobHistoryPlugin 记录每次作业执行历史
  • 监听器实现自定义日志和告警
  • 第三方工具如 QuartzminQuartz.NetUI(基于 Vue 的定时任务管理系统)提供可视化任务管理

4. 动态作业管理

  • 将作业配置存储在数据库或配置中心,通过代码动态注册而非硬编码
  • 利用 IScheduler 接口实现运行时的暂停/恢复/删除
  • 慎用 StartNow() 方法,避免应用启动时大量任务同时执行

十一、资源与扩展

  • 官网:https://www.quartz-scheduler.net
  • GitHub:https://github.com/quartznet/quartznet
  • 文档:https://www.quartz-scheduler.net/documentation
  • 扩展包:
    Quartz.Extensions.Hosting:ASP.NET Core 托管服务。
    Quartz.Serialization.SystemTextJson:JSON 序列化支持。
    Quartz.Plugins:提供日志、异常处理等插件。

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

相关文章

  • ASP.NET微信公众号之用户分组管理web页面

    ASP.NET微信公众号之用户分组管理web页面

    这篇文章主要为大家详细介绍了ASP.NET微信公众号之用户分组管理web页面,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-11-11
  • jQuery+Ajax用户登录功能的实现

    jQuery+Ajax用户登录功能的实现

    前几天把jbox源码修改成仿QQ空间模拟窗口后发现有很多人在关注。今天就贴一下我利用该模拟窗口实现的用户登录功能的代码。
    2009-11-11
  • C#将DataTable转化为List<T>

    C#将DataTable转化为List<T>

    本文给大家讲解的是如何使用C#将DataTable数据源转化为List<T>泛型集合(已知T类型) 的方法和示例,有需要的小伙伴可以参考下
    2015-06-06
  • 轻量级ORM框架Dapper应用之实现DTO

    轻量级ORM框架Dapper应用之实现DTO

    本文详细讲解了使用Dapper实现DTO的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • 详解ASP.NET Core 之 Identity 入门(二)

    详解ASP.NET Core 之 Identity 入门(二)

    本篇文章主要介绍了ASP.NET Core 之 Identity 入门,主要负责对用户的身份进行认证,有兴趣的可以了解一下。
    2016-12-12
  • ASP.NET MVC实现下拉框多选

    ASP.NET MVC实现下拉框多选

    这篇文章介绍了ASP.NET MVC实现下拉框多选的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • jquery.pagination +JSON 动态无刷新分页实现代码

    jquery.pagination +JSON 动态无刷新分页实现代码

    jquery.pagination +JSON 动态无刷新分页实现代码,需要的朋友可以参考下。
    2011-12-12
  • Asp.net 中使用GridView控件实现Checkbox单选

    Asp.net 中使用GridView控件实现Checkbox单选

    在GridView控件中,第0列有放一个CheckBox控件,现想实现对CheckBox进行单选,怎么实现呢?下面小编通过本文给大家分享Asp.net 中使用GridView控件实现Checkbox单选功能,一起看看吧
    2017-07-07
  • asp.net 多数据库支持的思考

    asp.net 多数据库支持的思考

    最近一直在思考如何做一个支持多种数据库的程序,打印了很多的资料,在.NET 2.0中,新增加了DbProviderFactory抽象工厂类,让数据层基类可以实现多种数据库,但在数据访问层中的参数部分我觉得是个麻烦。
    2009-07-07
  • asp.net DataSet进行排序

    asp.net DataSet进行排序

    关于对已经绑定的DataSet的排序的问题。
    2009-07-07

最新评论