C#怎么实现定时任务 C#如何用Timer和Quartz.NET创建定时执行的后台任务【技巧】
System.Threading.Timer最适合作为后台长任务的轻量轮询方案因其单线程回调避免线程池耗尽而System.Timers.Timer适合秒/分钟级简单任务但需防并发Quartz.NET适用于复杂调度需求但配置复杂。用 System.Timers.Timer 做简单轮询但别指望它精准它适合每秒/每分钟级的轻量任务比如心跳上报、缓存刷新。底层靠线程池回调不保证准时——如果 Elapsed 事件处理太慢下一次触发会被延迟甚至丢弃。必须手动调用 Start()构造完默认是停着的AutoReset 设为 false 才能只执行一次设为 true默认才循环但要注意避免重复触发多个 Elapsed 可能并发执行得自己加锁或用 Interlocked别在回调里直接 Stop() Start() 重置间隔会引发竞态改用 Change() 方法System.Threading.Timer 才有System.Timers.Timer 没这接口应用退出前记得调用 Dispose()否则可能阻止进程结束用 Quartz.NET 做生产级调度但别一上来就配集群它解决的是“每月最后一个周五上午9点发邮件”这类复杂表达式也支持持久化、故障恢复和分布式协调。但本地开发时RAMJobStore 就够用硬上 AdoJobStore 反而多出数据库连接、表初始化、连接泄漏等坑。触发器表达式写错常见于少写空格比如 0 0 9 ? * FRI 合法0 0 9?*FRI 直接抛 ParseExceptionJob 类必须有无参构造函数且不能是内部类反射实例化失败如果用 async 方法别直接 await 在 Execute() 里——Quartz 默认同步调用要封装成 Task.Run(() { ... }) 或改用 IJobFactory 注入异步上下文日志里出现 Trigger xxx is set to fire now, but scheduler is in standby mode说明调用了 Standby() 没 Start()为什么 System.Threading.Timer 比 System.Timers.Timer 更适合后台长任务前者回调只用一个线程可指定 ThreadPool 或 Null后者每次 Elapsed 都可能从线程池拿新线程容易打满池子。尤其当任务偶尔卡住前者最多阻塞下一次后者可能堆积一堆等待线程。 Cleanup.pictures 智能移除图片中的物体、文本、污迹、人物或任何不想要的东西