System.Threading.Timer首次回调立即执行,System.Timers.Timer首次触发需等待Interval;两者均使用线程池且不保证线程安全,回调并发执行可能引发竞态;UI更新须显式切换主线程,Dispose()为唯一可靠释放方式。
两者都用线程池线程执行回调,但触发逻辑根本不同:System.Threading.Timer 默认「立即执行首次回调(可设 dueTime = 0)」,之后按 period 间隔重复;而 System.Timers.Timer 总是「先等一个 Interval 才触发第一次 Elapsed 事件」,哪怕你刚调用 Start()。这意味着如果你需要「启动即干活」,Threading.Timer 更直接,不用额外手动调一次回调。
线程模型上,它们都**不保证线程安全**——回调本身在线程池中并发执行,但二者对「重入」的默认行为不同:
System.Threading.Timer:每次回调都是独立的线程池任务,若回调执行慢、且 period 小于执行耗时,会堆积多个并行回调,可能引发竞态(比如同时写同一个 Dictionary)System.Timers.Timer:同样不阻塞后续触发,AutoReset = true(默认)时,下一次 Elapsed 会在前一次还没结束时照常触发,也会并发执行两者回调都在后台线程运行,直接访问 TextBox.Text 或 Control.Invoke 会抛出 InvalidOperationException: “线程间操作无效”。这不是“定时器不安全”,而是 WinForms/WPF 的线程亲和性限制。
安全做法取决于场景:
System.Timers.Timer:可设置 SynchronizingObject = this(或任意 ISynchronizeInvoke 对象),它会自动把 Elapsed 事件封送到 UI 线程 —— 这是它比 Threading.Timer 唯一方便的地方Task.Run + await Dispatcher.InvokeAsync(...)(WPF)或 this.Invoke((MethodInvoker)delegate { ... })(WinForms)显式切换实测数据显示:System.Threading.Timer 实例几乎零内存分配(Allocated = 0 B),而 System.Timers.Timer 每个实例固定占用约 18 KB 内存(.NET Framework 4.8+)。高频创建/销毁大量定时器时,后者会明显推高 GC 压力。
精度方面:Threading.Timer 首次触发延迟更稳定(实测平均 ~15 ms),Timers.Timer 因事件路由开销,首次延迟波动大(实测达 90 ms 以上)。
最关键的是资源释放:
System.Th
reading.Timer 必须显式调用 Dispose(),否则回调可能持续执行(即使引用丢失),造成内存泄漏和意外触发System.Timers.Timer 同样必须 Dispose(),且建议配合 Stop() 使用;若只 Stop() 不 Dispose(),内部事件订阅和线程池句柄不会释放Dispose() 是唯一可靠方式用 System.Threading.Timer 当你:需要极致轻量、要精确控制首次触发时机、写后台服务/高性能中间件、能接受回调是纯委托(不带事件语义)。
用 System.Timers.Timer 当你:正在 WinForms 项目中且想省掉手动线程切换、需要 AutoReset/Enabled 这类状态属性、团队习惯事件编程模型、不介意多那 18 KB。
永远别用它们做耗时操作 —— 无论是读文件、发 HTTP 请求还是复杂计算,都应外包给 Task.Run 并加超时控制;否则线程池饥饿、定时漂移、甚至整个应用卡顿都会找*。