应根据场景选择:System.Timers.Timer适合WinForms/WPF需线程安全事件的UI更新(注意Invoke),System.Threading.Timer适合后台服务(需手动try/catch异常、强引用防GC回收、Dispose释放资源);Web应用应改用IHostedService。

直接用 System.Timers.Timer 或 System.Threading.Timer 就能跑定时任务,但选错类型、没处理好线程上下文或忘了释放资源,5分钟写的代码可能跑两天就挂了。
用 System.Timers.Timer 还是 System.Threading.Timer?
前者是基于事件的、线程安全的、适合 WinForms/WPF 主动触发 UI 更新(需注意跨线程);后者是轻量级委托回调、无事件模型、回调在线程池线程上执行,适合后台服务类定时操作。
-
System.Timers.Timer的Elapsed事件默认在 ThreadPool 线程触发,不能直接更新 WinForms 控件,得用Control.Invoke或 WPF 的Dispatcher.Invoke -
System.Threading.Timer不抛异常到主线程——如果回调里崩了,你根本收不到错误,建议包一层try/catch - Web 应用(如 ASP.NET Core)别用这两个做长期定时任务,IIS 可能回收进程或应用池休眠,应改用
IHostedService+BackgroundService
Timer 启动后不触发?常见原因
最常踩的坑不是代码写错,而是对象被 GC 提前回收了——尤其 System.Threading.Timer 没被任何变量引用时,哪怕设置了 dueTime,也可能秒退。
- 必须持有对 Timer 实例的强引用,比如存为类字段:
private Timer _timer;,而不是局部变量 -
System.Timers.Timer.AutoReset默认为true,设成false后只触发一次,记得手动调Start() -
Interval单位是毫秒,写1000是 1 秒,写60是 60 毫秒,别手抖少个零 - 首次触发延迟由
dueTime(Threading.Timer)或Start()调用时机决定,不是从构造就开始倒计时
如何安全停止并释放 Timer
不显式 Dispose() 或 Stop(),Timer 会一直占着线程池资源,甚至阻止应用退出。
-
System.Timers.Timer:调Stop()+Dispose(),且建议在finally或IDisposable.Dispose()中做 -
System.Threading.Timer:调Dispose()即可,它内部自动取消所有待执行回调 - 别在回调里直接
Dispose()当前 Timer——可能正处在回调执行中,要等回调退出后再 Dispose,或者用Interlocked.CompareExchange加锁控制状态 - 如果 Timer 是单例或静态的,注意多线程并发 Stop/Dispose,加锁或用
Lazy<timer></timer>初始化
真正麻烦的不是怎么启动 Timer,而是它跑着跑着突然不响了——往往因为没捕获回调异常、没保持引用、或部署环境限制了后台线程存活。留个心眼:加日志、设超时兜底、别信“它一直都在”。










