Quartz.NET 调度框架:从入门到封装实战

说实话,第一次接触 Quartz.NET 时我有点懵------一堆接口、调度器、触发器,还有那让人头疼的 Cron 表达式。但熬过那个坎之后,它成了我最信任的任务调度工具。

它适合什么场景?

  • 企业级复杂调度:比如需要每年、每月、特定星期几执行的任务
  • 需要持久化和集群:任务跑一半服务器挂了?Quartz 可以恢复继续跑
  • 秒级精准调度:能精确到秒,且支持事务
  • 分布式环境:多台机器协同干活,任务不重复

需要留意的几点

  1. 学习曲线确实陡------比 Hangfire 和 FluentScheduler 复杂不少
  2. 如果想可靠,得配持久化存储(数据库)
  3. 集群配置挺折腾,得踩几个坑才能跑顺
  4. 简单场景别用它------杀鸡用牛刀,自己找麻烦

快速上手:三步跑起来

1. 安装 NuGet

bash 复制代码
Install-Package Quartz

2. 写一个任务类

实现 IJob​ 接口,把要干的活塞进 Execute 方法。

csharp 复制代码
using Quartz;
using System;
using System.Threading.Tasks;

namespace QuartzExample
{
    [DisallowConcurrentExecution] // 防止同一任务并发跑两次
    public class MailJobTest : IJob
    {
        public async Task Execute(IJobExecutionContext context)
        {
            try
            {
                Console.WriteLine("开始发邮件......");
                // 实际业务逻辑
            }
            catch (Exception ex)
            {
                Console.WriteLine($"翻车了: {ex.Message}");
            }
        }
    }
}

3. 启动调度器并安排任务

csharp 复制代码
using Quartz;
using Quartz.Impl;
using System.Threading.Tasks;

namespace QuartzExample
{
    public class QuartzScheduler
    {
        private IScheduler _scheduler;

        public async Task StartScheduler()
        {
            var factory = new StdSchedulerFactory();
            _scheduler = await factory.GetScheduler();
            await _scheduler.Start();

            // 定义活儿
            var job = JobBuilder.Create<MailJobTest>()
                .WithIdentity("MailJob", "MailGroup")
                .Build();

            // 定义啥时候干(每5秒一次)
            var trigger = TriggerBuilder.Create()
                .WithIdentity("MailTrigger", "MailTriggerGroup")
                .WithCronSchedule("0/5 * * * * ?")
                .Build();

            // 绑定干活的人和干活的时间
            await _scheduler.ScheduleJob(job, trigger);
        }

        public async Task StopScheduler()
        {
            if (_scheduler != null)
                await _scheduler.Shutdown();
        }
    }
}

4. 调用入口

csharp 复制代码
using System;
using System.Threading.Tasks;

namespace QuartzExample
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var scheduler = new QuartzScheduler();
            await scheduler.StartScheduler();

            Console.WriteLine("调度器跑起来了,按任意键停掉它...");
            Console.ReadKey();

            await scheduler.StopScheduler();
        }
    }
}

高级技巧:处理耗时任务

如果任务执行时间可能超过调度间隔,一定要加上 [DisallowConcurrentExecution],否则上一次没跑完下一次又开始了,数据可能乱套。

csharp 复制代码
[DisallowConcurrentExecution]
public class LongRunningJob : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        await Task.Delay(5000); // 模拟慢操作
        Console.WriteLine("耗时任务跑完");
    }
}

封装一套好用的工具类

上面那个 QuartzScheduler 类只能管一个任务。实际项目中往往要管理多个任务,而且经常和 WinForms 或 WPF 界面联动。下面这个封装版本更贴近实战:

csharp 复制代码
using Quartz;
using Quartz.Impl;
using System.Threading.Tasks;

namespace UploadLogiData.Quartzs
{
    public class QuartzScheduler
    {
        private IScheduler _scheduler;

        // 假设有个主窗体 Form1
        public static Form1 Form { get; set; }

        public async Task ExLogiQuartz()
        {
            Form?.DisplayListboxMsg("任务调度启动!");

            var factory = new StdSchedulerFactory();
            _scheduler = await factory.GetScheduler();
            await _scheduler.Start();

            // 任务1:每5秒检查一次下载任务
            var job1 = JobBuilder.Create<LogiDownloadJob>()
                .WithIdentity("LogiJob", "LogiGroup")
                .Build();
            var trigger1 = TriggerBuilder.Create()
                .WithIdentity("LogiTrigger", "LogiTriggerGroup")
                .WithCronSchedule("0/5 * * * * ?")
                .Build();
            await _scheduler.ScheduleJob(job1, trigger1);

            // 任务2:每天下午3:10发邮件
            var job2 = JobBuilder.Create<MailJobTest>()
                .WithIdentity("MailJob", "MailGroup")
                .Build();
            var trigger2 = TriggerBuilder.Create()
                .WithIdentity("MailTrigger", "MailTriggerGroup")
                .WithCronSchedule("0 10 15 * * ?")
                .Build();
            await _scheduler.ScheduleJob(job2, trigger2);
        }

        public IScheduler Scheduler => _scheduler;

        public async Task ShutdownScheduler()
        {
            if (_scheduler != null)
                await _scheduler.Shutdown();
        }
    }
}

调用方式

csharp 复制代码
using System;
using System.Threading.Tasks;

namespace UploadLogiData
{
    class Program
    {
        public static async Task Main(string[] args)
        {
            // 关联主窗体(如果有)
            QuartzScheduler.Form = new Form1();

            var scheduler = new QuartzScheduler();
            await scheduler.ExLogiQuartz();

            Console.WriteLine("调度器已启动,按任意键关闭...");
            Console.ReadKey();
            await scheduler.ShutdownScheduler();
        }
    }
}

小提醒 :如果你的项目没有 WinForms 依赖,可以直接去掉 Form 静态属性,用日志输出代替。


再说说 Cron 表达式

Quartz 的 Cron 表达式比 Linux 的多了字段,一共 7 个字段(年可选)。刚接触时容易写错,建议用在线工具验证。

格式秒 分 时 日 月 星期 [年]

字段 允许值 特殊字符
0--59 , - * /
0--59 , - * /
小时 0--23 , - * /
1--31 , - * ? / L W C
1--12 / JAN--DEC , - * /
星期 1--7 / SUN--SAT , - * ? / L C #
年(可选) 空 / 1970--2099 , - * /

常用例子(直接拿来用):

表达式 含义
0 0 12 * * ? 每天中午12点
0 15 10 ? * MON-FRI 工作日每天上午10:15
0 0/5 14 * * ? 每天14:00~14:55,每5分钟一次
0 10,44 14 ? 3 WED 3月的每个星期三的14:10和14:44
0 15 10 L * ? 每月最后一天上午10:15
0 15 10 ? * 6#3 每月第三个星期五上午10:15
0 0 23 1/5 * ? 从每月1号开始,每5天晚上11点
0 11 11 11 11 ? 每年11月11日11:11(光棍节彩蛋)

常见坑

  • 日字段和星期字段不能同时用 *,必须有一个用 ? 表示"不指定"
  • 0/5*/5 效果一样,都是从0开始每5单位
  • 年份一般省略,除非你非要限制到某几年
相关推荐
止语Lab22 分钟前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活22 分钟前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困28 分钟前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn28 分钟前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户2986985301434 分钟前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户772831049084037 分钟前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金44 分钟前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛1 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒1 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境1 小时前
提升 Python 代码健壮性的方法大盘点
后端·python