三種時間人《.NET中的Timer(1)》

Timer在.Net中也是個挺有趣的族群,在.Net Framework中有三種不一樣的Timer,分別是Windows.Forms.Timer、System.Timers.Timer、System.Threading.Timer。這三個時間人在某些地方有點相同,也有許多地方大異其趣,所以我一直覺得他們是很有意思的。

       Timer在.Net中也是個挺有趣的族群,在.Net Framework中有三種不一樣的Timer,分別是Windows.Forms.Timer、System.Timers.Timer、System.Threading.Timer。這三個時間人在某些地方有點相同,也有許多地方大異其趣,所以我一直覺得他們是很有意思的。

       先來看看MSDN是如何說明這三個Timer,在MSDN的[伺服器端計時器簡介] 有一段話是這麼說的:

       <以下節錄自MSDN>

伺服器計時器、Windows 計時器和執行緒計時器

Visual Studio 和 .NET Framework 中有三個計時器控制項,也就是可於 [工具箱] 的 [元件] 索引標籤上看到的伺服器端計時器、在 [工具箱] 的 [Windows Form] 索引標籤上看到的標準 Windows 架構計時器,以及只能以程式方式使用的執行緒計時器。Windows 架構計時器從 Visual Basic 版本 1.0 就有了,且一直維持著並無本質上的改變。這個計時器最適合在 Windows Form 應用程式中使用。伺服器端的計時器是傳統計時器的更新,它在伺服器的環境中最適合使用。執行緒計時器是簡單的輕量計時器,使用回呼方法而不使用事件,同時由執行緒集區執行緒提供服務。

在 Win32 的架構下有兩種執行緒:UI 執行緒,以及背景工作執行緒。UI 執行緒在大多數的時間裡都保持著閒置的狀態並且等待訊息到達它的訊息迴圈中。一旦收到訊息,它便處理這個訊息然後等待下一個訊息的到達。另外,背景工作執行緒則是用於執行背景處理而不使用訊息迴圈。Windows 計時器和伺服器端計時器兩者都使用 Interval 屬性執行。執行緒計時器的間隔是在 Timer 建構函式中設定。計時器是針對不同目的而設計的,依執行緒的處理方式做為辨識:

  • Windows 計時器是設計用在單一執行緒的環境之下,在此,利用 UI 執行緒來執行處理。Windows 計時器的正確率限制在 55 毫秒。這些傳統的計時器需要使用者的程式碼中有一個可以使用的 UI 訊息幫浦,並且一直從相同的執行緒作業,或是將呼叫整理到另外一個執行緒。對於一個 COM 元件,這可能對效能有不利的影響。

  • 伺服器端計時器是為了在多執行緒環境下使用背景工作執行緒而設計的。因為它們使用不同的架構,伺服器端計時器可能要比 Windows 計時器更為精確。伺服器端計時器可以在執行緒間移動以處理被引發的事件。

  • 執行緒計時器對於執行緒上不提取訊息的案例中相當有用。例如,Windows 架構計時器仰賴作業系統的計時器支援,而如果您沒有提取執行緒上的訊息,與計時器關聯的事件就不會發生。執行緒計時器在這種情況中更有用。

       老實說,第一次看的時候還真是有看沒有懂,這篇文章大部份的字我都認識,但連起來之後就完全搞糊塗了,那就只好自己動手做做看,體會一下這些內容是在描述些什麼。

       首先來瞧瞧最傳統的Windows.Forms.Timer,這種計時器應該是最簡便使用的,簡便到可以直接在設計表單畫面時直接從工具箱拉一個元件來用,這個計時器的時間間隔是以Interval 屬性來設定時間﹝以千分之一秒為單位﹞,用過Windows.Forms.Timer的人應該都知道,不過特別強調的一點是在Windows.Forms.Timer的Interval 屬性是存在一些限制的,詳細可以參考[MSDN:Windows Form Timer 元件的 Interval 屬性限制] 。簡要而言有幾個重點:

        (1)你只能設定 1~64,767毫秒,也就是一個間隔最大只有一分鐘出頭。 『2009/04/19註記:這個限制現在似乎不成立,我後來發現新版的MSDN文件已經取消了這一項說明[MSDN:(.NET 3.5)  Windows Form Timer 元件的 Interval 屬性限制];奇怪的是,我是用VB2005(.NET 2.0 SP1),卻也沒有這項限制,所以有可能一開始是微軟沒有修正到這個說明的內容。』
        (2)它的時間比另外兩個計時器不準。
        (3)如果在Tick事件中要處理的程序時間太長,它可能會出包。

       既然如此,那這個計時器還有存在的必要嗎?我覺得它最大的優點是容易使用,而且不需要使用委派就可以直接呼叫表單畫面的控制項─這對於已經慣於寫多執緒的網友們當然不算什麼困擾,但對於不習於撰寫多執行緒程式,或是程式只要處理簡單的循環程序,Windows.Forms.Timer不失為一個方便的選擇。

       現在來看看Windows.Forms.Timer的示範,在以下畫面中只有幾個簡單的控制項,其中NumericUpDown1控制項可以調整nterval 屬性值,另外寫了兩個用來測試Tick Event的方法,程式內容沒什麼新奇的:

Public Class Form1
    Dim FormTimer As New Windows.Forms.Timer
    Dim iTickTimes As Integer = 1
    Private Sub BTN_FrmT_start_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BTN_FrmT_start.Click
        FormTimer.Interval = NumericUpDown1.Value
        '可以修改使用不同的Handler Method,觀察TextBox的內容是否有所不同
        ' AddHandler FormTimer.Tick, AddressOf myTimerTick01 'Form timer 用Tick事件程序1
        AddHandler FormTimer.Tick, AddressOf myTimerTick02 'Form timer 用Tick事件程序2
        FormTimer.Start()
    End Sub
    Private Sub myTimerTick01(ByVal sender As System.Object, ByVal e As System.EventArgs)
        TB_F.Text &= "#" & iTickTimes & " Tick Begin:" & Environment.TickCount & vbCrLf
        Dim i As Integer
        For i = 0 To 1
            System.Threading.Thread.Sleep(1)
        Next

        Application.DoEvents()
        TB_F.Text &= "#" & iTickTimes & " Tick End:" & Environment.TickCount & vbCrLf
        iTickTimes += 1
    End Sub
    Private Sub myTimerTick02(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Dim imyTicks As Integer
        imyTicks = iTickTimes
        iTickTimes += 1
        TB_F.Text &= "#" & imyTicks & " Tick Begin:" & Environment.TickCount & vbCrLf
        Dim i As Integer
       For i = 0 To 1
            System.Threading.Thread.Sleep(1)
        Next

        Application.DoEvents()
        TB_F.Text &= "#" & imyTicks & " Tick End:" & Environment.TickCount & vbCrLf
    End Sub
    Private Sub BTN_FrmT_stop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BTN_FrmT_stop.Click
        FormTimer.Stop()
        TB_F.Text &= "Stop:" & Environment.TickCount & vbCrLf
    End Sub
    Private Sub BTN_Clear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BTN_Clear.Click
        TB_F.Clear()
        iTickTimes = 1
    End Sub
End Class

     FormTimer2

      

       首先,我們將Interval設定為1,使用myTimerTick02方法來個瘋狂跑馬:

        所有的End訊號居然都發生在Timer.Stop之後,而且順序上看起來也很奇特,這表示說,如果我們不讓Timer Stop,在沒有足夠時間完成Tick事件所呼叫的方法的狀態下,它是永遠無法完成該完成的工作,證實了[如果在Tick事件中要處理的程序時間太長,它可能會出包] ;至於時間間隔不正確的問題,可以看到兩個Begin之間的差距在30毫秒上下,這可能是因為我的電腦根本沒法在這麼短的時間切換到下一個Tick,所以這個數據可能有點不客觀,不過等會將間隔放大後,就可以看出個大概了。

 

 

 

  

FormTimer3

 

       這一次咱們將Interval設為100,同樣使用myTimerTick02方法來個瘋狂跑馬:

        看起來這次比較乖巧了,有按照順序一個循環接一個循環的完成工作。這邊來驗證時間的問題,在右方的結果中,#2的Tcik Begin 減去 #1的Tick Begin是109;#4減去#3則是110,看起來就是有差一點點,果然時間並不是很準,不過差距很小也沒啥好挑替的。

       這程式中還附了另一個myTimerTick01,各位可以試著使用它來當做Tick呼叫的Handler Method,看看和myTimerTick02會出現哪些不同的結果。下一篇會再來聊聊System.Timers.Timer吧!

      

 

 

這個範例程式是以VB2005寫的,可以在以下超連結下載FormTimerTest.rar

 

 

 

 

[2013年註: 這篇有一些謬誤之處, 詳請參考時間人補遺]