.NET Framework中定时器timer的单线程与多线程使用讲解

2019-05-22 18:38:26刘景俊

多线程定时器使用线程池来允许少量线程服务多个定时器。这意味着,回调方法或Elapsed事件每次可能会在不同的线程上触发。此外,不论之前的Elapsed是否完成执行,Elapsed总是几乎按时触发。因此,回调方法或事件处理器必须是线程安全的。

多线程定时器的精度依赖于操作系统,通常是在 10-20 ms 的区间。如果需要更高的精度,你可以使用本地互操作(native interop)来调用 Windows 多媒体定时器,可以让精度提升到 1 ms。它定义在 winmm.dll 中,首先调用timeBeginPeriod来通知操作系统你需要更高的定时器精度,然后调用timeSetEvent来启动多媒体定时器。当使用完成后,调用timeKillEvent停止定时器,最后调用timeEndPeriod通知操作系统你不在需要更高的定时器精度了。可以通过搜索关键字 dllimport winmm.dll timesetevent 在网上找到完整的例子。

2.单线程定时器Permalink

.NET Framework 提供了两个定时器,为消除WPF 和 Windows Forms 应用程序的线程安全问题而设计:

System.Windows.Threading.DispatcherTimer(WPF)
System.Windows.Forms.Timer(Windows Forms)
单线程定时器不是被设计成能在其特定的环境外工作的。例如,如果在 Windows 系统服务应用程序中使用 Windows Forms 定时器,Timer事件不会触发!

它们暴露的成员都像System.Timers.Timer一样(Interval、Tick、Start和Stop),并且用法也类似。但是不同之处在于其内部是如何工作的。它们不是使用线程池来产生定时器事件,WPF 和 Windows Forms 定时器依赖于 UI 模型的底层消息循环机制(message pumping mechanism)。意味着Tick事件总是在创建该定时器的那个线程触发,在通常的程序中,它也就是管理所有 UI 元素和控件的那个线程。这有很多好处:

单线程计时器比较安全,对于更新 Windows Forms controls或者WPF这种简单任务来说更方便。在WPF或Windows Forms中安全的调用方法的SynchronizingObject对象。
单线程计时器是被设计成属于他们执行环境的计时器,如果你在一个Windows服务应用程序中使用Windows Forms的Timer,timer 事件并不会被触发,只有在对应的环境下才会被触发。
像System.Timers.Timer一样,他们也提供了相同的成员(Interval,Tick,Start,Stop),但是他们内部的工作原理不同,WPF和Windows Forms的计时器使用消息循环机制来取代线程池产生消息的机制。

你可以不必考虑线程安全。
新的Tick在之前的Tick完成执行前不会触发。
你可以直接在Tick时间事件的处理代码中更新 UI 控件,而不需要调用Control.Invoke或Dispatcher.Invoke。
这听起来好的难以置信,直到你意识到使用这些定时器的程序并不是真正的多线程,不会有并行执行。一个线程服务于所有定时器,并且还处理 UI 事件。这带来了单线程定时器的缺点: