Delphi 中的 TTimer 和 .NET Framework 中的 Timer 的計時週期研究

 
Delphi 中的 Timer 的機制究竟是什麼樣的?

有意查閱了一下 Delphi 的幫助文檔,對於 TTimer 類,它的說明如下:

TTimer is used to simplify calling the Windows API timer functions SetTimer and KillTimer, and to simplify processing the WM_TIMER messages. Use one timer component for each timer in the application.

The execution of the timer occurs through its OnTimer event. TTimer has an Interval property that determines how often the timer抯 OnTimer event occurs. Interval corresponds to the parameter for the Windows API SetTimer function.

Caution:    Limitations on the total number of timers system-wide are system dependent.

它清楚地表明它是通過調用  Windows API 函數並處理 WM_TIMER 消息來完成計時功能的。

查看了 TTimer 的源代碼,發現 TTimer 組件本身並沒有創建線程類,它的機制是通過創建一個窗口句柄,然後將窗口句柄傳送給被調用的 Windows API 函數 SetTimer(),SetTimer 會自動地創建一個計時器,並在計時週期過期時向窗口句柄發送 WM_TIMER 消息,而 Timer 控件則從消息泵中捕獲該消息,然後調用計時週期的處理事件。也就是說,TTimer 組件所觸發的計時事件,實際就是在主線程(UI線程)上執行的事件。它在主線程上執行的事件,不可能並行執行,只會是排序執行的。

Delphi 中,它的關鍵代碼如下:

constructor TTimer.Create(AOwner: TComponent);
begin
  inherited
Create(AOwner);
  FEnabled := True;
  FInterval := 1000;
  //創建句柄,供接收消息使用。
  FWindowHandle := Classes.AllocateHWnd(WndProc);
end;

destructor TTimer.Destroy;
begin
  //銷燬句柄
  Classes.DeallocateHWnd(FWindowHandle);
  inherited Destroy;
end;

procedure TTimer.WndProc(var Msg: TMessage);
begin
  //這是消息泵循環的過程,當接收到 WM_TIMER 消息時,立即調用 Timer 方法來觸發事件。
  with Msg do
    if
Msg = WM_TIMER then
      try

        Timer;
      except
        Application.HandleException(Self);
      end
    else

      Result := DefWindowProc(FWindowHandle, Msg, wParam, lParam);
end;

procedure TTimer.UpdateTimer;
begin
  //進入下一個計時器調用週期
  KillTimer(FWindowHandle, 1);
  if (FInterval <> 0) and FEnabled and Assigned(FOnTimer) then
    if
SetTimer(FWindowHandle, 1, FInterval, nil) = 0 then
      raise
EOutOfResources.Create(SNoTimers);
end;

procedure TTimer.Timer;
begin
 
//調用 UI 主線程上的事件,實際就是在組件中編寫的事件代碼。
  if Assigned(FOnTimer) then FOnTimer(Self);
end;

無獨有偶,我查看了 Microsoft 所公佈的 .NET 2.0 中 System.Windows.Forms.Timer 的源代碼,這個 Timer 組件也是被聲明用於 UI 的計時器,發現它同 Delphi 一樣是在 Timer 之中建立一個窗口句柄,然後調用 SetTimer,並通過在消息循環中抓取 WM_TIMER 消息來響應計時功能。因此,這種 Timer 只能用於 UI 線程中,而不能用於 Service 服務程序和其它程序,因爲它們無法提供窗口句柄的消息循環。 .NET 專門爲這些程序提供了另外的兩個 Timer 組件,而這兩個 Timer 具有更高的精確度。

在 .NET Framework 中,這三種類型的 Timer,存在於不同的命名空間之下。

  • System.Windows.Forms.Timer
  • System.Timer.Timer
  • System.Threading.Timer

我針對前兩種 Timer,分別進行了計時測試,以驗證應用於窗體的 Timer 和應用於 Service 程序 Timer 之間的區別。測試內容爲:

Timer 的計時週期設置爲 100ms。在 Timer 中進行隨機休眠,這個休眠時間可能 <100ms,更多的是 >100ms,這樣就造成了 Timer 的每次執行事件的時長不同。每組進行19次事件測試。

正常情況下,Timer 組件應是 100ms 就會觸發一次計時事件。在計時事件中,首先記錄本次事件中的初始時間和隨機休眠時長,然後使用隨機數結果調用 Sleep() 進行指定時長的休眠。以下爲測試結果。

System.Windows.Forms.Timer

測試結果見下圖。可以看到的是,每次計時週期記錄的初始時間基本上是上次的計時週期時間與隨機休眠時間之和。也就是說,Timer 的事件是排序的,上一次事件未執行完成時,下次事件不會被觸發,而當休眠時間 < 100ms 時,則計時器依照一開始的定點時間進行觸發。

image

  

System.Timer.Timer

下圖是用 System.Timer.Timer 執行的結果,System.Timer.Timer 是用於 Windows Service 程序的計時器。從下圖可以看到,即使 Sleep 延遲超過了 100ms,仍然沒有阻礙它定時地觸發記錄。它每次的執行間隔基本上保持在 100ms 左右,實際上它沒有受到事件執行時間長短的影響。

image

綜上所述,如果需要精確的計時週期且互不干擾地執行指定的事件,不應當使用基於 UI 的 Timer 組件,而應當選擇使用線程或者其它更精確的非 UI Timer 組件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章