在.Net Framework Class Library(FCL)中,System.Threading命名空間下定義了一個Timer類,這就是常用的一個計時器。實際上FCL總共提供了如下幾種計時器:
1、System.Threading.Timer
在實際的開發中,這個類出現和使用頻率非常高,下面就重點談談它的一些基礎概念和應用執行個體。
(1)、常用的構造器
public Timer(TimerCallback callback, object state, int dueTime, int period); public Timer(TimerCallback callback, object state, long dueTime, long period); public Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period); public Timer(TimerCallback callback, object state, uint dueTime, uint period);
參數說明:
a、callback:望文生意,肯定表示一個回調,它是標識希望由一個線程池線程回調的方法。當然它的類型必須和System.Threading.TimerCallback委託類型匹配,如下所示:
public delegate void TimerCallback(object state);
b、state:每次調用回調方法,向回調方法傳遞的狀態資料,如沒有,可以為null;
c、dueTime:在首次調用回調方法之前要等待多少毫秒。如希望立刻調用回調方法,該參數指定為0即可。
d、period:指定了以後每次調用回調方法之前要等待多少毫秒(理解成下一次和本次調用的時間間隔即可)。如果為該參數傳遞Timeout.Infinite(或者直接寫-1),線程池線程只調用回調方法一次(那也就沒有必要用計時器了)。
(2)、基本工作原理
在內部(原文應該指CLR內),線程池為所有Timer對象只使用了一個線程。這個線程知道下一個Timer對象在什麼時候到期(計時器還有多久觸發)。下一個Timer對象到期時,線程就會喚醒,在內部調用ThreadPool的QueueUserWorkItem,將一個工作項目添加到線程池的隊列中,使你的回調方法得到調用。
注意:如果回調方法的執行時間很長,計時器可能在上個回調還沒有完成的時候再次觸發(對於執行時間很長的任務,實際開發中通常不使用線程池,而是直接使用new一個Thread)。這可能會造成多個線程池線程同時調用你的方法(方法的重疊覆蓋?)。為瞭解決這個問題,Jeffrey Richter建議我們這樣使用Timer:
a、為period指定Timeout.Infinite。這樣,計時器就只觸發一次;
b、在回調方法中,調用Timer的Change方法來指定一個新的dueTime,並再次為period指定Timeout.Infinite。Change方法的幾個重載版本:
public bool Change(int dueTime, int period); public bool Change(long dueTime, long period); public bool Change(TimeSpan dueTime, TimeSpan period); public bool Change(uint dueTime, uint period);
Timer還有一個Dispose方法,允許完全取消計時器。
(3)、範例程式碼
internal static class TimerDemo{ private static Timer s_timer; public static void Main() { Console.WriteLine("Main thread: starting a timer"); using (s_timer = new Timer(ComputeBoundOp, 5, 0, Timeout.Infinite)) { Console.WriteLine("Main thread: Doing other work here..."); Thread.Sleep(10000); // Simulating other work (10 seconds) } // Calls Dispose to cancel the timer now Console.Read(); } // This method's signature must match the TimerCallback delegate private static void ComputeBoundOp(Object state) { // This method is executed by a thread pool thread Console.WriteLine("In ComputeBoundOp: state={0}", state); Thread.Sleep(1000); // Simulates other work (1 second) // Have the Timer call this method again in 2 seconds s_timer.Change(2000, Timeout.Infinite); // When this method returns, the thread goes back // to the pool and waits for another work item }}
這個樣本是<<CLR via C#>>中的源碼,雖然簡單,卻貫穿實現了建立Timer,Timer定時工作到Timer的銷毀一整個生命週期。using語句我們可以使用try finally替換,在finally語句塊中顯式調用Dispose方法。注意,程式中輸出了四次“In ComputeBoundOp: state=5”,您不妨多想一想為什麼只有4次。而如果我們把Main方法的這一行:
Thread.Sleep(10000); // Simulating other work (10 seconds)
注釋掉,程式有時候會拋出“程式無法訪問已釋放對象”的異常,而且每次輸出的結果可能還不一樣。之所以這麼說,是因為using語句不能保證靜態Timer立刻被記憶體回收行程回收。您不妨自己動手一試。
2、System.Timers.Timer
這個類據說基本上就是System.Threading.Timer的封裝過的“整過容的副產品”。當計時器到期觸發時,會導致CLR將事件放到線程池的隊列中。該類派生自System.ComponentModel的Component類,允許VS將計時器對象放到設計平面上,並公開了它的屬性和事件(不就是一個控制項嗎,難道?)。
3、System.Windows.Forms.Timer
構造一個該類的執行個體,相當於告訴Windows將一個計時器和調用線程關聯。當這個計時器被觸發時,Windows將一條及時訊息注入線程的訊息佇列。線程必須執行一個訊息泵來提取這些訊息,並把它們派遣給想要的回調方法。注意,所有這些工作都只由一個線程完成——設定計時器的線程保證就是執行回調方法的線程。這還意味著你的計時器方法不會由多個線程並發執行。
4、System.Windows.Threading.DispatcherTimer
這個類是System.Windows.Forms的Timer在WPF和SilverLight應用程式中的等價物。通俗地說,這個類也就是System.Windows.Forms的Timer在WPF和SilverLight下的一個馬甲。擅於改頭換面新瓶裝舊酒在MS的技術領域中也不是一天兩天的事情了。
在Jeffrey Richter 的<<CLR via C#>>中關於計時器有很精練的說明,其中他說“事實上,我個人從來不用System.Timers.Timer類,建議你也不要用它”,他的這種態度蠻好玩的。反正對於這個類我倒是用過不少,而且一直沒出過什麼差錯,就算是”山寨“或者”馬甲“也不是從來都是一無是處。
參考:
Jeffrey Richter <<CLR via C#>>
http://msdn.microsoft.com/zh-cn/library/system.threading.timer.aspx