.NET Framework中定時器timer的單執行緒與多執行緒使用講解
如果你需要使用規律的時間間隔重複執行一些方法,最簡單的方式是使用定時器(timer)。與下邊的例子相比,定時器可以便捷、高效地使用記憶體和資源:
?1 2 3 4 5 6 7 |
new
Thread ( delegate () {
while
(enabled)
{
DoSomeAction();
Thread.Sleep (TimeSpan.FromHours (24));
}
}).Start();
|
這不僅僅會永久佔用一個執行緒,而且如果沒有額外的程式碼,DoSomeAction每天都會發生在更晚的時間。定時器解決了這些問題。
.NET Framework 提供了 4 種定時器。下邊兩個類是通用的多執行緒定時器:
(1)System.Threading.Timer
(2)System.Timers.Timer
另外兩個是專用的單執行緒定時器:
(3)System.Windows.Forms.Timer (Windows Forms 的定時器)
(4)System.Windows.Threading.DispatcherTimer (WPF 的定時器)
多執行緒定時器更加強大、精確並且更加靈活,而單執行緒定時器對於一些簡單的更新 Windows Forms 和 WPF 控制元件的任務來說是安全的,並且更加便捷。
1.多執行緒定時器Permalink
System.Threading.Timer是最簡單的多執行緒定時器:它僅僅有一個構造方法和兩個普通方法(取悅於極簡主義者,還有本書作者!)。在接下來的例子中,一個定時器在 5 秒鐘之後呼叫Tick方法來列印 “ tick… “,之後每秒列印一次直到使用者按下回車鍵:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using
System;
using
System.Threading;
class
Program
{
static
void Main()
{
// 首次間隔 5000ms,之後間隔 1000ms
Timer tmr =
new Timer (Tick,
"tick..." , 5000, 1000);
Console.ReadLine();
tmr.Dispose();
// 停止定時器並執行清理工作
}
static
void Tick ( object
data)
{
// 這裡執行在一個執行緒池執行緒上 Console.WriteLine (data);
// 列印 "tick..."
}
}
|
之後可以通過呼叫Change方法來改變定時器的時間間隔。如果你希望定時器只觸發一次,可以指定Timeout.Infinite作為構造方法的最後一個引數。
.NET Framework 在System.Timers名稱空間下提供了另一個名字相同的定時器類。它只是封裝了 System.Threading.Timer,並在使用完全相同的底層引擎的前提下提供額外的便利。下面是增加功能的簡介:
(1)實現了Component,允許用於 Visual Studio 的設計器中。
(2)Interval屬性代替了Change方法。
(3)Elapsed事件代替了回撥委託。
(4)Enabled屬性用於開始或停止定時器(預設值是false)。
(5)Start和Stop方法,避免對Enabled屬性感到困惑。
(6)AutoReset標識來指定是否為可重複的事件(預設為true)。
SynchronizingObject屬性提供Invoke和BeginInvoke方法,用於在 WPF 和 Windows Forms 控制元件上安全呼叫方法。
這有個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using
System;
using
System.Timers; // 名稱空間是 Timers 而不是 Threading
class
SystemTimer
{
static
void Main()
{
Timer tmr =
new Timer();
// 無需任何引數
tmr.Interval = 500;
tmr.Elapsed += tmr_Elapsed;
// 使用事件代替委託
tmr.Start();
// 開啟定時器
Console.ReadLine();
tmr.Stop();
// 停止定時器
Console.ReadLine();
tmr.Start();
// 重啟定時器
Console.ReadLine();
tmr.Dispose();
// 永久停止定時器
}
static
void tmr_Elapsed ( object
sender, EventArgs e)
{
Console.WriteLine ( "Tick" );
}
}
|
多執行緒定時器使用執行緒池來允許少量執行緒服務多個定時器。這意味著,回撥方法或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 事件。這帶來了單執行緒定時器的缺點:
除非Tick事件處理器執行的很快,否則 UI 會失去響應。
這使得 WPF 和 Windows Forms 定時器僅適用於小任務,通常就是那些更新 UI 外觀的任務(例如,顯示時鐘或倒計時)。否則,你就需要多執行緒定時器。
在精度方面,單執行緒定時器與多執行緒定時器類似(幾十毫秒),但是通常精度更低,因為它們會被其它 UI 請求(或其它定時器事件)推遲。
單執行緒計時器基於Windows訊息迴圈,應用程式會同步的處理計時器的訊息。會發現UI介面相應速度比較慢。解決這個問題的方法是使用多執行緒計時器。
單執行緒計時器的缺點:除非Tick事件的處理程式碼執行的非常快,否則UI介面會變得響應很慢。所以 WPF和Windows Forms的計時器都非常適合小任務,尤其是介面更新的任務。例如時鐘和計數顯示。否則,你需要一個多執行緒計時器