2020国产成人精品视频,性做久久久久久久久,亚洲国产成人久久综合一区,亚洲影院天堂中文av色

分享

定時器:.NET框架類庫中定時器類的比較

 文成Y 2007-09-19

定時器:.NET框架類庫中定時器類的比較


原著:Alex Calvo

翻譯:lxhui



原文出處:MSDN Magazine February 2004(Timer...)

原代碼下載: TimersinNET.exe (126KB)

本文章假定你熟悉C#

概要  
  不論在客戶端應(yīng)用程序還是服務(wù)器組件(包括窗口服務(wù))定時器通常扮演一個重要的角色。寫一個高效的定時器驅(qū)動型可管理代碼要求對程序流程有一個清晰的理解及掌握.NET線程模型的精妙之處。.NET框架類庫提供了三種不同的定時器類:System.Windows.Forms.Timer, System.Timers.Timer, 和System.Threading.Timer。每個類為不同的場合進行設(shè)計和優(yōu)化。本文章將研究這三個類并讓你理解如何及何時應(yīng)該使用哪一個類。


  Microsoft® Windows®里的定時器對象當行為發(fā)生時允許你進行控制。定時器一些最常用的地方就是有規(guī)律的定時啟動一個進程,在事件之間設(shè)置間隔,及當進行 圖形工作時維護固定的動畫速度(而不管處理函數(shù)的速度)。在過去,對于使用Visual Basic®的開發(fā)者來說,定時器甚至用來模擬多任務(wù)。
  正如你所期望的那樣,對于你需要應(yīng)對的不同場合微軟為你裝備了一些工具。在.NET框架類庫中有三種不同的定時器類:System.Windows.Forms.Timer,System.Timers.Timer,和System.Threading.Timer。頭兩個類出現(xiàn)在Visual Studio® .NET的工具箱窗口,這兩個定時器控件都允許你直接把它們拖拽到Windows窗體設(shè)計器或組件類設(shè)計器上。如果你不小心,這就是麻煩的開始。
  Visual Studio .NET工具箱上的Windows窗體頁和組件頁(見Figure 1)都有定時器控件。非常容易的錯誤地使用它們當中的一個,或者更糟糕的是,根本意識不到它們的不同。僅當目標是Windows窗體設(shè)計器時才使用Windows窗體頁上的定時器控件。這個控件將在你的窗體上放置一個Systems.Windows.Forms.Timer類的實例。像工具箱上的其它控件一樣,你可以讓Visual Studio .NET處理其生成或者你自己手動的實例和初始化這個類。

  Figure 1  定時器控件

  在組件頁上的定時器控件可以被安全的用在任何類中。這個控件創(chuàng)建了一個System.Timers.Timer類的實例。如果你正在使用Visual Studio .NET工具箱,無論是Windows窗體設(shè)計器還是組件類設(shè)計器你都可以安全的使用這個類。在Visual Studio .NET中當你設(shè)計一個派生于System.ComponentModel.Component的類時使用組件類設(shè)計器。System.Threading.Timer類不出現(xiàn)在Visual Studio .NET工具箱窗口上。它稍微有點復雜但提供了一個更高級別的控件,稍后你會在本文章中看到。

  Figure 2  例子程序

  讓我們首先研究System.Windows.Forms.Timer和System.Timers.Timer類。這兩個類有著非常相似的對象模型。稍后我將探索更加高級的System.Threading.Timer類。Figure 2 是我將在整個文章引用的例子程序的一個屏幕快照。這個應(yīng)用程序?qū)屇惬@得對這幾個定時器類的清晰的理解。你可以從本文章的開始鏈接處下載完整的代碼并試驗它。

System.Windows.Forms.Timer
  如果你在找一個節(jié)拍器,你已經(jīng)走錯了地方了。這個定時器類引發(fā)的定時器事件是同你的窗口應(yīng)用程序的其余代碼相同步的。這意味著正在執(zhí)行的代碼從來不會被這個定時器類的實例所搶占(假設(shè)你不調(diào)用Application.DoEvents)。就像一個典型窗體程序里的其它代碼一樣,任何駐留在一個定時器事件處理函數(shù)(指的是該類型的定時器類)中的代碼也是使用應(yīng)用程序的UI線程所執(zhí)行。在空閑時候,該UI線程同樣要對應(yīng)用程序的窗體消息隊列中的所有消息進行負責。這不僅包括由這個定時類引發(fā)的消息,也包括窗體API消息。無論何時你的程序不忙于做其它事情時該UI線程就處理這些消息。
  在Visual Studio .NET之前如果你寫過Visual Basic代碼,你可能知道在一個窗口應(yīng)用程序里當正在執(zhí)行一個事件處理函數(shù)時讓你的UI線程去響應(yīng)其它窗體消息的唯一方法就是調(diào)用Application.DoEvents方法。就像Visual Basic一樣,從.NET框架中調(diào)用Application.DoEvents能夠產(chǎn)生許多問題。Application.DoEvents產(chǎn)生了對UI消息泵的控制,讓你對所有未處理的事件進行處理。這能夠改變我剛才提到的所期望的執(zhí)行路徑。如果為了處理由該定時器類產(chǎn)生的定時器事件而在你的代碼中有一個Application.DoEvents的調(diào)用,你的程序流程可能會被打斷。這會產(chǎn)生不希望的行為并使調(diào)試困難。
  運行例子程序就會使這個定時器類的行為變得清楚。單擊程序的Start按鈕,接著單擊Sleep按鈕,最后單擊Stop按鈕,將會產(chǎn)生下面的輸出結(jié)果:
System.Windows.Forms.Timer Started @ 4:09:28 PM--> Timer Event 1 @ 4:09:29 PM on Thread:UIThread--> Timer EVENT 2 @ 4:09:30 PM on Thread: UIThread--> Timer Event 3 @ 4:09:31 PM on Thread: UIThreadSleeping for 5000 ms...--> Timer Event 4 @ 4:09:36 PM on Thread: UIThreadSystem.Windows.Forms.Timer Stopped @ 4:09:37 PM
  例子程序設(shè)置System.Windows.Forms.Timer類的間隔屬性為1000毫秒。正如你所看到的,當UI線程正在睡眠(5秒)期間如果定時器事件處理函數(shù)仍然繼續(xù)捕捉定時器事件的話,當睡眠線程再次被喚醒的時候應(yīng)該有5個定時器事件被顯示——在UI線程睡眠時每秒鐘一個。然而,當UI線程在睡眠時定時器卻保持掛起狀態(tài)。
  對System.Windows.Forms.Timer的編程不能再簡單了——它有一個非常簡單和可直接編程的接口。Start和Stop方法實際上提供了一個設(shè)置使能屬性的改變方法(其本身是對Win32®的SetTimer和KillTimer功能的一個包裝)。我剛才提到的間隔屬性,名字本身就說明了問題。即使技術(shù)上你可以設(shè)置間隔屬性低到1毫秒,但你應(yīng)該知道在.NET框架文檔中指出這個屬性大約精確到55毫秒(假定UI線程對于處理是可用的)。
  捕捉由System.Windows.Forms.Timer類實例引發(fā)的事件是通過感知一個標準的EventHandler委托的標記事件來處理的,就像下面的代碼片斷所示:
System.Windows.Forms.Timer tmrWindowsFormsTimer = new    System.Windows.Forms.Timer();tmrWindowsFormsTimer.Interval = 1000;tmrWindowsFormsTimer.Tick += new    EventHandler(tmrWindowsFormsTimer_Tick);tmrWindowsFormsTimer.Start();...private void tmrWindowsFormsTimer_Tick(object sender,    System.EventArgs e){    //Do something on the UI thread...}
System.Timers.Timer
  .NET框架文檔指出System.Timers.Timer類是一個服務(wù)器定時器,是為多線程環(huán)境進行設(shè)計和優(yōu)化。該定時器類的實例能夠被多個線程安全地訪問。不像System.Windows.Forms.Timer,System.Timers.Timer缺省的,將在一個工作者線程上調(diào)用你的定時器事件處理函數(shù),該工作者線程是從公共語言運行時(CLR)線程池中獲得。這意味著在你的逝去的時間處理函數(shù)代碼中必須遵從Win32編程的黃金規(guī)則:除了創(chuàng)建該控件實例的線程之外,一個控件的實例從來不被任何其它的線程所訪問。
  System.Timers.Timer提供了一個簡單的方法處理這樣的困境——暴露一個公共的SynchronizingObject屬性。把該屬性設(shè)置為一個窗體實例(或者窗體上的一個控件)將保證你的事件處理函數(shù)代碼運行在SynchronizingObject被實例化的同一個線程里。
  如果你使用了Visual Studio .NET工具箱,Visual Studio .NET自動的設(shè)置SynchronizingObject屬性為當前的窗體實例。首先它設(shè)定該定時器的SynchronizingObject屬性使其在功能上同System.Windows.Forms.Timer類一樣。對于大部分功能,的確是這樣。當操作系統(tǒng)通知System.Timers.Timer類所允許的定時時間已過去,定時器使用SynchronizingObject.Begin.Invoke方法在一個線程上去執(zhí)行事件委托,該線程是創(chuàng)建SynchronizingObject的線程。事件處理函數(shù)將被阻塞直到UI線程能夠處理它。然而不像System.Windows.Forms.Timer類一樣,該事件最終仍然能夠被引發(fā)。像你在Figure 2中看到的,當UI線程不能夠處理時System.Windows.Forms.Timer不會引發(fā)事件,可是當UI線程可用時System.Timers.Timer卻會排隊等候處理。
  Figure 3是如何使用SynchronizingObject屬性的例子。使用例子程序并通過選擇System.Timers.Timer的radio按鈕你可以分析這個類,并按照執(zhí)行System.Windows.Forms.Timer類行為的同樣順序運行該類,這樣就會產(chǎn)生Figure 4的輸出結(jié)果。
  正如你所看到的,它不會跳過一個跳動——即使UI線程在睡眠。在每一個事件間隔就有一個時間消失事件處理會被排隊執(zhí)行。因為UI線程在睡眠,所以當UI線程一旦被喚醒例子程序就會列出5個定時器事件(4到8)并能夠處理處理函數(shù)。
  正如我早先提到的,System.Timers.Timer類成員非常類似與System.Windows.Forms.Timer。最大的區(qū)別就在與System.Timers.Timer類是對Win32可等待定時對象的一個包裝,并在工作者線程上產(chǎn)生一個時間片消失事件而不是在UI線程上產(chǎn)生一個時間標記事件。時間片消失事件必須與一個同ElapsedEventHandler委托像匹配的事件處理函數(shù)相連接。事件處理函數(shù)接受一個ElapsedEventArgs類型的參數(shù)。
  除了標準的EventArgs成員,ElapsedEventArgs類暴露了一個公共的SignalTime屬性,它包含了一個精確的定時器時間片消失的時間。因為這個類支持不同線程的訪問,除了時間消失事件所在的線程,應(yīng)該相信它的Stop方法能夠被其它線程所調(diào)用。這會潛在的導致消失事件被引發(fā)即使其Stop方法已經(jīng)被調(diào)用。你可以把SignalTime和Stop方法調(diào)用的時間進行比較來解決這個問題。
  System.Timers.Timer也提供了AutoReset屬性來決定當時間片消失事件引發(fā)后是繼續(xù)進行還是只這一次。要記住在定時器開始后重設(shè)間隔屬性會導致當前計數(shù)為0。比如,設(shè)置了一個5秒的間隔,在間隔被改變?yōu)?0秒時3秒已經(jīng)過去了,那么下一個定時器事件將會在上一個定時器事件13秒后發(fā)生。

System.Threading.Timer
  第三個定時器類來自System.Threading名字空間。我愿意說這是所有定時器類中最好的一個,但這會引起誤導。舉一個例子,我驚訝的發(fā)現(xiàn)對于駐留在System.Threading名字空間的這個類天生就不是線程安全的。(很明顯,這不意味著它不能以線程安全的方式使用)。這個類的可編程接口同其它兩個類也不一致,它稍微有點麻煩。
  不像我開始描述的兩個定時器類,System.Threading.Timer有四個重載構(gòu)造函數(shù),就像下面這樣:
public Timer(TimerCallback callback, object state, long dueTime,	     long period);public Timer(TimerCallback callback, object state, UInt32 dueTime,	     UInt32 period);public Timer(TimerCallback callback, object state, int dueTime,	     int period);public Timer(TimerCallback callback, object state, TimeSpan dueTime,	     TimeSpan period);
第一個參數(shù)(callback)要求一個TimerCallback的委托,它指向一個方法,該方法具有下面的結(jié)構(gòu):
public void TimerCallback(object state);
第二個參數(shù)(state)可以為空或者是包含程序規(guī)范信息的對象。在每一個定時器事件被調(diào)用時該state對象作為一個參數(shù)傳遞給你的定時回調(diào)函數(shù)。記住定時回調(diào)功能是在一個工作者線程上執(zhí)行的,所以你必須確保訪問state對象的線程安全。
第三個參數(shù)(dueTime)讓你定義一個引發(fā)初始定時器事件的時間。你可指定一個0立即開始定時器或者阻止定時器自動的開始,你可以使用System.Threading.Timeout.Infinite常量。
第四個參數(shù)(period)讓你定義一個回調(diào)函數(shù)被調(diào)用的時間間隔(毫秒)。給該參數(shù)定義一個0或者Timeout.Infinite可以阻止后續(xù)的定時器事件調(diào)用。
  一旦構(gòu)造函數(shù)被調(diào)用,你仍然可以通過Change方法改變dueTime和period。該方法有下面四種重載形式:
public bool Change(int dueTime, int period);public bool Change(uint dueTime, uint period);public bool Change(long dueTime, long period);public bool Change(TimeSpan dueTime, TimeSpan period);
  下面是我在例子程序中用到的開始和停止該定時器的代碼:
//Initialize the timer to not start automatically...System.Threading.Timer tmrThreadingTimer = newSystem.Threading.Timer(new		       TimerCallback(tmrThreadingTimer_TimerCallback),		       null, System.Threading.Timeout.Infinite, 1000);//Manually start the timer...tmrThreadingTimer.Change(0, 1000);//Manually stop the timer...tmrThreadingTimer.Change(Timeout.Infinte, Timeout.Infinite);
  正如你所期望的那樣,通過選擇System.Threading.Timer類運行例子程序會產(chǎn)生同你看到的System.Timers.Timer類一樣的輸出結(jié)果。因為TimerCallback功能也是在工作者線程上被調(diào)用,沒有一個跳動被跳過(假設(shè)有工作者線程可用)。Figure 5顯示了例子程序的輸出結(jié)果。
  不像System.Timers.Timer類,沒有與SynchronizingObject相對應(yīng)的屬性被提供。任何請求訪問UI控件的操作都必須通過控件的Invoke或BeginInvoke方法被列集

定時器的線程安全編程
  為了最大限度的代碼重用,三種不同類型的定時器事件都調(diào)用了同樣的ShowTimerEventFired方法,下面就是三個定時器事件的處理函數(shù):
private void tmrWindowsFormsTimer_Tick(object sender,    System.EventArgs e){    ShowTimerEventFired(DateTime.Now, GetThreadName());}private void tmrTimersTimer_Elapsed(object sender,    System.TimersElapsedEventArgs e){    ShowTimerEventFired(DateTime.Now, GetThreadName());}private void tmrThreadingTimer_TimerCallback(object state){    ShowTimerEventFired(DateTime.Now, GetThreadName());}
  正如你所看到的,ShowTimerEventFired方法采用當前時間和當前線程名字作為參數(shù)。為了區(qū)別工作者線程和UI線程,在例子程序的主入口點設(shè)置CurrentThread對象的名字屬性為"UIThread"。GetThreadName幫助函數(shù)返回Thread.CurrentThread.Name值或者當Thread.CurrentThread.IsThreadPoolThread屬性為真時返回"WorkerThread"。
  因為System.Timers.Timer和System.Threading.Timer的定時器事件都是在工作者線程上執(zhí)行的,所以在事件處理函數(shù)中的任何用戶交互代碼都不是馬上進行的,而是被列集等候返回到UI線程上進行處理。為了這樣做,我創(chuàng)建了一個ShowTimerEventFiredDelegate委托調(diào)用:
private delegate void    ShowTimerEventFiredDelegate    (DateTime eventTime,     string threadName);
  ShowTimerEventFiredDelegate允許ShowTimerEventFired方法在UI線程上調(diào)用它自己,Figure 6顯示了發(fā)生這一切的代碼。
  通過查詢InvokeRequired屬性可以非常容易的知道你是否從當前線程可以安全的訪問Windows窗體控件。在這個例子中,如果列表框的InvokeRequired屬性為真,窗體的BeginInvoke方法就可以被ShowTimerEventFired方法調(diào)用,然后再被ShowTimerEventFiredDelegate方法調(diào)用。這能夠保證列表框的Add方法在UI線程上執(zhí)行。
  正如你所看到的,當你編寫異步定時器事件時有許多問題需要意識到。在使用System.Timers.Timer和System.Threading.Timer之前我推薦你閱讀Ian Griffith的文章“Windows Forms:Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads”, 該文刊登在MSDN雜志的2003年2月份的期刊上。

處理定時器事件重入
  當和異步定時器事件打交道時,如由System.Timers.Timer和System.Threading.Timer產(chǎn)生的定時器事件,有另外一個細微之處你需要考慮。問題就是必須處理代碼重入。如果你的定時器事件處理函數(shù)代碼執(zhí)行時間比你的定時器引發(fā)定時器事件的時間間隔要長,你預先又沒有采取必要的措施保護防止多線程訪問你的對象和變量,你就會陷入調(diào)試的困境??匆幌孪旅娴拇a片斷:
private int tickCounter = 0;private void tmrTimersTimer_Elapsed(object sender,    System.Timers.ElapsedEventArgs e){    System.Threading.Interlocked.Increment(ref tickCounter);    Thread.Sleep(5000);    MessageBox.Show(tickCounter.ToString());}
  假設(shè)你的定時器間隔屬性設(shè)置為1000毫秒,你也許會奇怪當?shù)谝粋€信息框彈出時顯示的值是5。這是因為在這5秒期間第一個定時器事件正在睡眠,而定時器卻在不同的工作者線程上繼續(xù)產(chǎn)生時間消失事件。因此,在第一個定時器事件處理完成之前tickCounter變量被增加了5次。注意我使用了Interlocked.Increment方法以線程安全的方式增加tickCounter變量的值。也有其它方法可以這樣做,但是Interlock.Increment是為這種操作而特別設(shè)計的。
  解決這種問題的簡單方法就是在你的事件處理函數(shù)代碼塊中暫時禁止定時器,接著再允許定時器,就像下面的代碼:
private void tmrTimersTimer_Elapsed(object sender,    System.Timers.ElapsedEventArgs e){    tmrTimers.Enabled = false;    System.Threading.Interlocked.Increment(ref tickCounter);    Thread.Sleep(5000);    MessageBox.Show(tickCounter.ToString());    tmrTimersTimer.Enabled = true;}
  有了這段代碼,消息框就會每5秒鐘顯示一次,就像你所期望的那樣,tickCounter的值每次只增加1。另外一些可選的原始同步對象就是Monitor或mutex去確保所有將來的事件被排隊直到當前的事件處理函數(shù)執(zhí)行完成。

結(jié)論
  為了快速方便的看到.NET框架中這三個定時器類的不同之處,見Figure 7對三個類的比較。當使用定時器類時有一點你要考慮的就是是否可以使用Windows調(diào)度器去定期的運行標準的可執(zhí)行程序來更簡單的解決問題。

作者簡介
  Alex Calvo 是微軟認證的.NET解決方案開發(fā)者。當他不進行閱讀,編碼或思考時,他就彈吉他。Alex Calvo 的聯(lián)系地址:acalvo@hotmail.com
譯者結(jié)束語
  在翻譯過程中一些術(shù)語、單詞或詞組總是找不到合適的漢語解釋, 現(xiàn)已經(jīng)將它們列入 MTT 術(shù)語表,希望大家能給出更好的解釋:
  1. Elapsed——消失,時間消失。意思就是指所設(shè)置的定時時間間隔過去了。在文中主要有Elapsed Event, Elapsed Event
  2. handler, Elapsed timer等詞。
  3. toolbox——工具箱
  4. windows tab——窗體頁
  5. component tab——組件頁
  6. handler——處理器,實際就是所說的函數(shù)或方法 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多