1、 Timer種類
1.System.Threading.Timer
2.System.Timers.Timer
3.System.Windows.Forms.Timer
4.System.Windows.Threading.DispatcherTimer
2、 Timer用法及簡介
A.System.Threading.Timer
提供以指定的時間間隔對線程池線程執行方法的機制。 無法繼承此類。
主要有四個參數。
CallBack,一個返回值為void,參數為object的委托,也是計時器執行的方法。
state,計時器執行方法的的參數。
dueTime,調用 callback 之前延遲的時間量(以毫秒為單位)。
period,調用 callback 的時間間隔。
例:System.Threading.Timer tm=new System.Threading.Timer(tick,null,1000,2000);//1秒后開始計時,2秒后調用tick事件。
特點:多線程計時器,精確,而且可擴展性強。
B. System.Timers.Timer
MSDN說明:
Timer 組件是基於服務器的計時器,它使您能夠指定在應用程序中引發 Elapsed 事件的周期性間隔。然后可以操控此事件以提供定期處理。例如,假設您有一台關鍵性服務器,必須每周 7 天、每天 24 小時都保持運行。可以創建一個使用 Timer 的服務,以定期檢查服務器並確保系統開啟並在運行。如果系統不響應,則該服務可以嘗試重新啟動服務器或通知管理員。
基於服務器的 Timer 是為在多線程環境中用於輔助線程而設計的。服務器計時器可以在線程間移動來處理引發的 Elapsed 事件,這樣就可以比 Windows 計時器更精確地按時引發事件。有關基於服務器的計時器的更多信息,請參見“基於服務器的計時器介紹”。
基於 Interval 屬性的值,Timer 組件引發 Elapsed 事件。可以處理該事件以執行所需的處理。例如,假設您有一個聯機銷售應用程序,它不斷向數據庫發送銷售訂單。編譯發貨指令的服務分批處理訂單,而不是分別處理每個訂單。可以使用 Timer 每 30 分鍾啟動一次批處理。
Timer.Elapsed
事件達到間隔時發生。如果將 Enabled 設置為 true 並將 AutoReset 設置為 false,則 Timer 在第一次達到間隔時僅引發一次 Elapsed 事件。如果在已啟動 Timer 后設置 Interval,則重置計數。例如,如果將間隔設置為 5 秒,然后將 Enabled 設置為 true,則計數將在設置 Enabled 時開始。如果在計數為 2 秒時將間隔重置為 10 秒,則 Elapsed 事件在 Enabled 設置為 true 的 12 秒之后第一次引發。
Elapsed 事件在 ThreadPool 線程上引發。如果 Elapsed 事件的處理時間比 Interval 長,在另一個 ThreadPool 線程中將會再次引發此事件。因此,事件處理程序應當是可重入的。
Eg:
public static void Test() { System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(OnTimerdEnt); timer.Interval = 50;//50ms timer.Enabled = true;//cycle
GC.KeepAlive(timer); }
private static void OnTimerdEnt(object obj, ElapsedEventArgs e) { Console.WriteLine("Hello World!"); }
|
注意 |
Elapsed 事件在 ThreadPool 線程上引發,因此事件處理方法可以運行在一個線程上,同時 Stop 方法調用運行在另一個線程上。這可能導致在調用 Stop 方法后引發 Elapsed 事件。此主題的代碼示例演示了一種防止爭用條件的方法。 |
下面的代碼示例演示了一種防止調用 Stop 方法的線程在當前正在執行的 Elapsed 事件結束之前繼續進行,以及防止兩個 Elapsed 事件同時執行事件處理程序(通常稱為可重入性)的方法。
此示例執行 100 次測試運行。每次運行測試時,計時器以 150 毫秒的間隔啟動。事件處理程序使用 Thread.Sleep 方法來模擬一個長度在 50 和 200 毫秒之間隨機變化的任務。測試方法還啟動一個控制線程,此線程等待一秒然后停止計時器。如果此控制線程停止計時器時有一個事件正被處理,則此線程必須等待此事件完成后才能繼續進行。
Interlocked.CompareExchange(Int32,Int32,Int32) 方法(原子操作)重寫用於避免可重入性和防止控制線程在正在執行的事件結束前繼續進行。事件處理程序使用 CompareExchange(Int32,Int32,Int32) 方法將一個控制變量設置為 1,但僅在此變量的當前值為零時才進行此設置。如果返回值為零,則此控制變量已被設置為 1 並且事件處理程序繼續進行。如果返回值不為零,則只是丟棄此事件以避免可重入性。(如果有必要執行每一個事件,Monitor 類將是一個同步這些事件的更好方法。)事件處理程序在結束時將控制變量設置回零。此示例記錄已執行的事件、由於可重入性而被丟棄的事件以及在調用 Stop 方法后發生的事件的總數。
控制線程使用 CompareExchange(Int32,Int32,Int32) 方法將控制變量設置為 -1(負一),但僅在此變量的當前值為零時才進行此設置。如果原子操作返回非零值,則當前有一個事件正在執行。控制線程等待並重試。此示例記錄控制線程等待一個事件完成的次數。
Eg:
private static int testRuns = 100; private static int testRunsFor = 1000; private static int timerInterval = 150; private static System.Timers.Timer Timer1 = new System.Timers.Timer(); private static Random rand = new Random(); private static int syncPoint = 0; private static int numEvents = 0; private static int numExecuted = 0; private static int numSkipped = 0; private static int numLate = 0; private static int numWaits = 0; [MTAThread] public static void Main() { Timer1.Elapsed += new ElapsedEventHandler(Timer1_ElapsedEventHandler); Timer1.Interval = timerInterval; Console.WriteLine(); for(int i = 1; i <= testRuns; i++) { TestRun(); Console.Write("\rTest {0}/{1} ", i, testRuns); } Console.WriteLine("{0} test runs completed.", testRuns); Console.WriteLine("{0} events were raised.", numEvents); Console.WriteLine("{0} events executed.", numExecuted); Console.WriteLine("{0} events were skipped for concurrency.", numSkipped); Console.WriteLine("{0} events were skipped because they were late.", numLate); Console.WriteLine("Control thread waited {0} times for an event to complete.", numWaits); } public static void TestRun() { syncPoint = 0; Timer1.Enabled = true; Thread t = new Thread(ControlThreadProc); t.Start(); t.Join(); } private static void ControlThreadProc() { Thread.Sleep(testRunsFor); Timer1.Stop(); bool counted = false; while (Interlocked.CompareExchange(ref syncPoint, -1, 0) != 0) { Thread.Sleep(0); if (!counted) { numWaits += 1; counted = true; } } } private static void Timer1_ElapsedEventHandler( object sender, ElapsedEventArgs e) { numEvents += 1; // int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0); if (sync == 0) { int delay = 50 + rand.Next(150); Thread.Sleep(delay); numExecuted += 1; syncPoint = 0; } else { if (sync == 1) { numSkipped += 1; } else { numLate += 1; } } }
|
小結:
上述兩個Timer都是多線程計時器。Timer每到間隔時間后就會激發響應事件,因此要申請線程來執行對應的響應函數,Timer將獲取線程的工作都交給了線程池來管理,
每到一定的時間后它就去告訴線程池:“我現在激發了個事件要運行對應的響應函數,麻煩你給我向操作系統要個線程,申請交給你了,線程分配下來了你就運行我給你的響應函數,
沒分配下來先讓響應函數在這兒排隊(操作系統線程等待隊列)”,消息已經傳遞給線程池了,Timer也就不管了,因為它還有其他的事要做(每隔一段時間它又要激發事件),
至於提交的請求什么時候能夠得到滿足,要看線程池當前的狀態,如果線程滿了就排隊,在有空線程時就會響應函數。否則就直接響應。
C. System.Windows.Forms.Timer
實現按用戶定義的時間間隔引發事件的計時器。 此計時器最宜用於 Windows 窗體應用程序中,並且必須在窗口中使用,單線程使用。由於單線程計時器基於Windows消息循環,應用程序會同步的處理計時器的消息。UI界面會相對響應速度很慢。此 Windows 計時器專為使用 UI 線程來執行處理的單線程的環境。 它要求用戶代碼有一個可用的用戶界面消息泵,並且始終在同一個線程操作或到另一個線程的調用封送。
D.System.Windows.Threading.DispatcherTimer
集成到Dispatcher隊列的計時器,在指定時間間隔和指定的優先級處理。
小結:
兩個是單線程計時器,其工作機制也和上面不同。計時器使用消息循環機制來取代線程池產生消息的機制。
這意味着Tick事件總是在創建timer的那個線程上執行,同時也意味着如果上一個Tick消息還未被處理,即使時間超過了間隔時間,在消息循環中也只存在一個Tick消息。
它們都是線程安全。一個Tick事件在前一個Tick事件被處理完畢前不會被觸發。你可以直接在Tick事件處理代碼中更新控件,不需要調用Control.Invoke或Dispatcher.Invoke.
文中有多處借鑒