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单位
  • 年份一般省略,除非你非要限制到某几年
相关推荐
Java编程爱好者7 小时前
MySQL事务实战:MySQL实例 · 隔离级别 · InnoDB实现机制
后端
砍材农夫7 小时前
物联网 基于netty构建mqtt服务udp支持
后端
JavaAgent架构师7 小时前
Spring AI接入OpenAI报错401/429?3种原因+完整解决代码
人工智能·后端
子兮曰7 小时前
AI Coding 为什么全选了 TUI?从 Claude Code 到 Codex CLI,终端架构的底层逻辑
前端·后端·ai编程
voyaqi7 小时前
从零设计企业级校验框架:Spring Boot + SPI 实战指南
spring boot·后端·log4j
XovH7 小时前
Django 从 0 到 1 打造完整电商平台:电商项目需求分析与数据库设计
后端
可乐泡枸杞7 小时前
《我用AI,一个月做出学了吗APP》
前端·人工智能·后端
huipeng9268 小时前
基于SpringCloud的博客系统
java·运维·后端·spring·spring cloud·微服务
神奇小汤圆8 小时前
一致性Hash算法:如何实现分布式系统中的高效数据分片?
后端