从System.Threading.Timer到Quartz.NET:C#定时任务的进化史,你还在用老古董?
🔥关注墨瑾轩,带你探索编程的奥秘!🚀
🔥超萌技术攻略,轻松晋级编程高手🚀
🔥技术宝库已备好,就等你来挖掘🚀
🔥订阅墨瑾轩,智趣学习不孤单🚀
🔥即刻启航,编程之旅更有趣🚀


C#定时任务的三大"江湖门派"与血泪史
一、System.Threading.Timer:老伙计,但有点"老年痴呆"
using System;
using System.Threading;
class Program
{
static void Main(string[] args)
{
// 创建一个定时器,设置5秒后执行,之后每5秒执行一次
// 注意:这里用的是System.Threading.Timer,不是System.Timers.Timer
// 为啥?因为它是.NET Framework的底层定时器,轻量级但有点"呆"
Timer timer = new Timer(TimerCallback, null, 5000, 5000);
Console.WriteLine("定时任务已启动,按任意键退出...");
Console.ReadKey();
// 关键:别忘了停止定时器,否则程序退出后定时器还在后台跑
// 否则,你可能会在生产环境看到"定时任务线程泄漏"的噩梦
timer.Dispose();
}
// 定时回调方法
private static void TimerCallback(object state)
{
// 这里是你的业务逻辑
Console.WriteLine($"定时任务执行时间:{DateTime.Now:HH:mm:ss.fff}");
// 重点:这个方法在后台线程执行,不是主线程
// 所以如果你要更新UI,会抛出跨线程访问异常
// 解决方案:使用Invoke或BeginInvoke,但这就复杂了
}
}
墨氏注解:
Timer类是.NET Framework的底层定时器,轻量级但功能简单TimerCallback在后台线程执行,不是主线程,所以不能直接操作UIDispose()必须调用,否则会导致资源泄漏(尤其是长时间运行的应用)- 坑点:如果回调方法执行时间超过设定间隔,下一次回调会等当前执行完再开始,可能导致任务堆积
“当年我用这个Timer写定时任务,结果在生产环境跑着跑着,服务器内存像被吸血鬼吸干了一样,排查了三天才发现是没调
Dispose()。老铁,别犯我这种低级错误啊!”
二、System.Timers.Timer:更"现代化",但有隐藏的"定时炸弹"
using System;
using System.Timers;
class Program
{
static Timer timer;
static void Main(string[] args)
{
// 创建定时器,设置5秒后执行,之后每5秒执行一次
timer = new Timer(5000);
// 绑定Elapsed事件,这是定时器的核心
timer.Elapsed += OnTimedEvent;
// 设置定时器自动触发,否则只触发一次
timer.AutoReset = true;
// 启动定时器
timer.Enabled = true;
Console.WriteLine("定时任务已启动,按任意键退出...");
Console.ReadKey();
// 停止定时器,避免资源泄漏
timer.Stop();
timer.Dispose();
}
// 定时器触发的事件处理方法
private static void OnTimedEvent(object sender, ElapsedEventArgs e)
{
// 这里是你的业务逻辑
Console.WriteLine($"定时任务执行时间:{e.SignalTime:HH:mm:ss.fff}");
// 重点:Elapsed事件在后台线程触发,同样不能操作UI
// 而且,如果回调方法执行时间超过间隔,下一次回调会延迟
// 但不会堆积,因为它是单线程执行
}
}
墨氏注解:
System.Timers.Timer是System.Threading.Timer的"升级版",面向服务器端应用Elapsed事件在后台线程触发,不能直接操作UIAutoReset属性:true表示自动重复触发,false表示只触发一次- 坑点:如果回调方法执行时间超过间隔,下一次回调会延迟,但不会堆积(因为是单线程)
“记得有一次,我写了个定时任务,每天凌晨三点处理数据。结果因为某次数据量大,处理时间超过了5分钟,导致下一次任务延迟了5分钟。产品经理半夜打电话质问我,‘为啥凌晨三点才处理完?’ 我当时就想:‘这不怪我,怪你的数据量太大!’ 但最后还是被骂惨了。”
三、Quartz.NET:定时任务的"瑞士军刀",优雅到哭
using Quartz;
using Quartz.Impl;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// 创建调度器工厂
ISchedulerFactory factory = new StdSchedulerFactory();
// 获取调度器实例
IScheduler scheduler = await factory.GetScheduler();
// 启动调度器
await scheduler.Start();
// 定义任务
IJobDetail job = JobBuilder.Create<SimpleJob>()
.WithIdentity("job1", "group1")
.Build();
// 定义触发器,每天凌晨2点执行
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger1", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(1440) // 每24小时执行一次
.RepeatForever())
.Build();
// 将任务与触发器关联
await scheduler.ScheduleJob(job, trigger);
Console.WriteLine("定时任务已启动,按任意键退出...");
Console.ReadKey();
// 关闭调度器
await scheduler.Shutdown();
}
}
// 任务实现
public class SimpleJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
// 这里是你的业务逻辑
Console.WriteLine($"定时任务执行时间:{DateTime.Now:HH:mm:ss.fff}");
// 重点:Execute方法在调度器线程中执行,不是主线程
// 但Quartz.NET处理了线程管理,你不用操心
return Task.CompletedTask;
}
}
墨氏注解:
Quartz.NET是一个功能强大的开源调度库,支持Cron表达式- Cron表达式:让定时任务变得无比灵活,比如"每天凌晨2点"、“每周一上午10点”、“每月最后一天”
- 任务在调度器线程中执行,你不用操心线程管理
- 优势:支持分布式调度、任务持久化、任务重试等高级功能
- 坑点:需要额外安装NuGet包,配置稍复杂,但值得
“用Quartz.NET之前,我写定时任务就像在用老式打字机,笨重又麻烦。用上它后,我直接把定时任务写成Cron表达式,比如’0 0 2 * * ?‘,表示每天凌晨2点执行。现在,我再也不用担心半夜被报警叫醒了。这玩意儿,简直是定时任务界的’瑞士军刀’!”
四、为什么你的定时任务总在半夜跑崩?——90%开发者踩过的坑
-
线程管理不当:定时任务在后台线程执行,如果回调方法执行时间过长,会导致任务堆积,服务器资源耗尽。
- 解决方案:使用
Quartz.NET或确保回调方法执行时间短
- 解决方案:使用
-
资源泄漏:忘记调用
Dispose()或Stop(),导致定时器线程一直运行,资源被泄漏。- 解决方案:在程序退出前,确保调用
Dispose()或Stop()
- 解决方案:在程序退出前,确保调用
-
时间精度问题:
System.Threading.Timer和System.Timers.Timer的精度有限,可能无法精确到毫秒。- 解决方案:使用
Quartz.NET,它提供了更高的时间精度
- 解决方案:使用
-
任务重复执行:如果回调方法执行时间超过间隔,可能会导致任务重复执行。
- 解决方案:使用
Quartz.NET的CronTrigger,避免重复执行
- 解决方案:使用
-
任务持久化:如果应用重启,定时任务会丢失。
- 解决方案:使用
Quartz.NET的JobStore,将任务持久化到数据库
- 解决方案:使用
“我曾经有个定时任务,每5分钟执行一次。结果因为某个数据量大,执行时间超过了5分钟,导致下一次任务延迟了5分钟。然后,下一次任务又因为数据量大,执行时间超过5分钟,导致延迟越来越长。最后,服务器CPU飙升到100%,线上服务直接崩了。现在想想,真是后怕啊!”
五、实战:从老古董到现代工具的进化之路
场景:我们需要一个定时任务,每天凌晨2点处理订单数据。
老古董方案(System.Timers.Timer):
using System;
using System.Timers;
class Program
{
static Timer timer;
static void Main(string[] args)
{
// 创建定时器,设置每天凌晨2点执行
// 注意:这里需要计算当前时间到凌晨2点的时间差
DateTime now = DateTime.Now;
DateTime nextRun = now.Date.AddDays(1).AddHours(2);
TimeSpan delay = nextRun - now;
timer = new Timer(delay.TotalMilliseconds);
timer.Elapsed += OnTimedEvent;
timer.AutoReset = true;
timer.Enabled = true;
Console.WriteLine("定时任务已启动,按任意键退出...");
Console.ReadKey();
timer.Stop();
timer.Dispose();
}
private static void OnTimedEvent(object sender, ElapsedEventArgs e)
{
// 处理订单数据
Console.WriteLine($"处理订单数据:{DateTime.Now:HH:mm:ss.fff}");
}
}
墨氏注解:
- 这种方式需要手动计算下一次执行时间,非常麻烦
- 如果应用重启,定时任务会丢失
- 无法精确到每天凌晨2点,因为时间计算可能有误差
现代方案(Quartz.NET):
using Quartz;
using Quartz.Impl;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
ISchedulerFactory factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler();
await scheduler.Start();
// 定义任务
IJobDetail job = JobBuilder.Create<OrderProcessingJob>()
.WithIdentity("orderProcessingJob", "orderProcessingGroup")
.Build();
// 定义触发器,每天凌晨2点执行
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("orderProcessingTrigger", "orderProcessingGroup")
.StartNow()
.WithCronSchedule("0 0 2 * * ?") // 每天凌晨2点
.Build();
await scheduler.ScheduleJob(job, trigger);
Console.WriteLine("订单处理定时任务已启动,按任意键退出...");
Console.ReadKey();
await scheduler.Shutdown();
}
}
public class OrderProcessingJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
// 处理订单数据
Console.WriteLine($"处理订单数据:{DateTime.Now:HH:mm:ss.fff}");
// 重点:这里可以做复杂的数据处理
// 而不用担心线程问题
return Task.CompletedTask;
}
}
墨氏注解:
- 使用Cron表达式,精确到每天凌晨2点
- 任务持久化,应用重启后仍能继续执行
- 无需手动计算时间,代码更简洁
- 任务管理更专业,支持任务重试、日志记录等
“以前用老方法,每天都要写一堆时间计算的代码,还容易出错。现在用Quartz.NET,一行Cron表达式搞定,代码简洁多了。而且,即使应用重启,定时任务也不会丢失,这感觉,就像给定时任务装了个’保险丝’,再也不用担心半夜被报警叫醒了。”
定时任务的"进阶之路",你还在等什么?
老码农的总结:
- 对于简单任务,
System.Threading.Timer或System.Timers.Timer足够用 - 对于复杂任务,尤其是需要精确控制、持久化、分布式调度的场景,
Quartz.NET是不二之选
墨氏金句:
“别再用老古董了!定时任务不是’能跑就行’,而是’要跑得稳、跑得准、跑得优雅’。Quartz.NET就是你的’定时任务瑞士军刀’,让你的代码优雅到哭。”
墨瑾轩的终极建议:
“别再让定时任务在半夜惊醒你了。从今天开始,用Quartz.NET,让你的定时任务跑得稳、跑得准、跑得优雅。记住,代码要优雅,定时任务更要优雅!”
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)