System.Threading.Timer首次回调立即执行,System.Timers.Timer首次触发需等待Interval;两者均使用线程池且不保证线程安全,回调并发执行可能引发竞态;UI更新须显式切换主线程,Dispose()为唯一可靠释放方式。

System.Threading.Timer 和 System.Timers.Timer 的触发时机与线程模型差异
两者都用线程池线程执行回调,但触发逻辑根本不同: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会在前一次还没结束时照常触发,也会并发执行
为什么不能直接在回调里更新 UI?怎么安全地切回主线程?
两者回调都在后台线程运行,直接访问 TextBox.Text 或 Control.Invoke 会抛出 InvalidOperationException: “线程间操作无效”。这不是“定时器不安全”,而是 WinForms/WPF 的线程亲和性限制。
安全做法取决于场景:
- WinForms 中用
System.Timers.Timer:可设置SynchronizingObject = this(或任意ISynchronizeInvoke对象),它会自动把Elapsed事件封送到 UI 线程 —— 这是它比Threading.Timer唯一方便的地方 - 通用方案(尤其控制台、服务、或跨平台):用
Task.Run+await Dispatcher.InvokeAsync(...)(WPF)或this.Invoke((MethodInvoker)delegate { ... })(WinForms)显式切换 - 千万别在回调里直接 new Form 或 ShowDialog() —— 即使切了线程,模态对话框仍可能卡死消息循环
内存、精度、Dispose:三个最容易被忽略的坑
实测数据显示:System.Threading.Timer 实例几乎零内存分配(Allocated = 0 B),而 System.Timers.Timer 每个实例固定占用约 18 KB 内存(.NET Framework 4.8+)。高频创建/销毁大量定时器时,后者会明显推高 GC 压力。
精度方面:Threading.Timer 首次触发延迟更稳定(实测平均 ~15 ms),Timers.Timer 因事件路由开销,首次延迟波动大(实测达 90 ms 以上)。
千博购物系统.Net能够适合不同类型商品,为您提供了一个完整的在线开店解决方案。千博购物系统.Net除了拥有一般网上商店系统所具有的所有功能,还拥有着其它网店系统没有的许多超强功能。千博购物系统.Net适合中小企业和个人快速构建个性化的网上商店。强劲、安全、稳定、易用、免费是它的主要特性。系统由C#及Access/MS SQL开发,是B/S(浏览器/服务器)结构Asp.Net程序。多种独创的技术使
最关键的是资源释放:
-
System.Threading.Timer必须显式调用Dispose(),否则回调可能持续执行(即使引用丢失),造成内存泄漏和意外触发 -
System.Timers.Timer同样必须Dispose(),且建议配合Stop()使用;若只Stop()不Dispose(),内部事件订阅和线程池句柄不会释放 - 别依赖析构函数 —— 它们都不实现终结器,
Dispose()是唯一可靠方式
选哪个?看这三句话就足够
用 System.Threading.Timer 当你:需要极致轻量、要精确控制首次触发时机、写后台服务/高性能中间件、能接受回调是纯委托(不带事件语义)。
用 System.Timers.Timer 当你:正在 WinForms 项目中且想省掉手动线程切换、需要 AutoReset/Enabled 这类状态属性、团队习惯事件编程模型、不介意多那 18 KB。
永远别用它们做耗时操作 —— 无论是读文件、发 HTTP 请求还是复杂计算,都应外包给 Task.Run 并加超时控制;否则线程池饥饿、定时漂移、甚至整个应用卡顿都会找上门。






