程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 多線程編程(16) - 多線程同步之 WaitableTimer (等待定時器對象)[續二]

多線程編程(16) - 多線程同步之 WaitableTimer (等待定時器對象)[續二]

編輯:Delphi

 想過沒有? WaitableTimer 是在 "定時等待", 前面例子中的 WaitForSingleObject 等待函數 "也在等待", 這就 "雙重等待" 了, 這不好, 太浪費資源.

  其實作為同步工具, 前面的幾種方法(事件、信號、臨界區)基本夠用了; WaitableTimer 的作用並不是為了重復前面的功能, 它的主要功用類似 TTimer 類; 譬如每隔多長時間執行一段代碼、或在指定的時間去執行一段代碼.

  既然有了方便的 TTimer, 何必再使用 WaitableTimer 呢?

  因為 WaitableTimer 比 TTimer 精確的多, 它的間隔時間可以精確到毫秒、它的指定時間甚至是精確到 0.1 毫秒;

  而 TTimer 驅動的 WM_TIMER 消息, 是消息隊列中優先級最低的, 也就是再同一時刻 WM_TIMER 消息總是被最後處理.

  還有重要的一點 WaitableTimer 可以跨線程、跨進程使用.

  繼續探討一個重要的點: 很多時候為了讓線程不沖突, 線程也在等待, 既然有等待, 那 WaitableTimer 非常精確的定時又有什麼價值呢? 對這個問題的思考, 可以讓我們很好地理解 APC 函數.

  SetWaitableTimer 有個回調函數(其實是個過程), Windows 要求它的格式是:

procedure TimerAPCProc(
 lpArgToCompletionRoutine: Pointer;
 dwTimerLowValue: DWord;
 dwTimerHighValue: DWord
); stdcall;

  函數名中有 APC 的字樣, 指示這是個 APC 函數(盡管這個名稱無所謂, 這是官方命名), 那什麼是 APC 函數?

  APC(Asyncroneus Procedure Call): 異步過程調用.

  原來每個線程除了有單獨的消息隊列, 還有一個 APC 隊列(等待執行的 APC 函數); 如果線程發現 APC 隊列中有情況, 馬上會跳過去執行, 執行完畢後才回來接著處理消息隊列.

 說起來麻煩, 使用的時候只按上面格式傳入函數指針就行; 不過能進入 APC 隊列的回調函數和其他回調函數還有一個很大的不同:

  SetWaitableTimer 按格式調用 APC 函數後, 需要在 "當前線程" 見到一個 "等待", 此 APC 函數才可以進入隊列.

  這好像很費解, 例說一下: APC 隊列有那麼高的優先級, 因為對資源的優先使用會對其他消息有很大的影響, 肯定不能隨便進入, 這是不是像生活中的貴賓席或貴賓通道?

  也就是說, 要進入 APC 隊列只有 SetWaitableTimer 的調用還不夠, 還要通過 "等待函數" 介紹一下.

  WaitForSingleObject 嗎? 不是, 它不夠級別; 下面是 Windows 認可的、可以介紹 APC 入列的等待函數:

SleepEx();
WaitForSingleObjectEx();
WaitForMultipleObjectsEx();
MsgWaitForMultipleObjectsEx();
SignalObjectAndWait();

  為什麼是用等待函數來把關? 因為上面幾個等待函數也可以等待是否有 APC 函數想入列.

  上面給出的幾個等待函數, 就 SleepEx 的參數最少, 先用它吧:

function SleepEx(
 dwMilliseconds: DWord; {毫秒數}
 bAlertable: BOOL    {布爾值}
): DWord; stdcall;
//第一個參數和 Sleep 的那個參數是一樣的, 是線程等待(或叫掛起)的時間, 時間一到不管後面參數如何都會返回.
//第二個參數如果是 False, SleepEx 將不會關照 APC 函數是否入列;
//若是 True, 只要有 APC 函數申請, SleepEx 不管第一個參數如何都會把 APC 推入隊列並隨 APC 函數一起返回.
//注意: SetWaitableTimer 和 SleepEx 必須在同一個線程才可以.


 

本例效果圖:

  多線程編程(16) - 多線程同步之 WaitableTimer (等待定時器對象)[續二]

  代碼文件:

unit Unit1;
interface
uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, ExtCtrls, StdCtrls;
type
 TForm1 = class(TForm)
  Button1: TButton;
  procedure Button1Click(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
 end;
var
 Form1: TForm1;
implementation
{$R *.dfm}
var
 hTimer: THandle;
{APC 函數(過程), 函數名和參數名可以不同, 格式必須如此}
procedure TimerAPCProc(lpArgToCompletionRoutine: Pointer; dwTimerLowValue: DWord;
 dwTimerHighValue: DWord); stdcall;
begin
 Form1.Text := IntToStr(StrToIntDef(Form1.Text, 0) + 1); {標題 + 1}
end;
procedure TForm1.Button1Click(Sender: TObject);
var
 DueTime: Int64;
begin
 hTimer := CreateWaitableTimer(nil, True, nil);
 DueTime := 0;
 if SetWaitableTimer(hTimer, DueTime, 0, @TimerAPCProc, nil, False) then
 begin
  SleepEx(INFINITE, True); {INFINITE 表示一直等}
 end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
 CloseHandle(hTimer);
end;
end.


 

 窗體文件:

object Form1: TForm1
 Left = 0
 Top = 0
 Caption = 'Form1'
 ClIEntHeight = 113
 ClIEntWidth = 203
 Color = clBtnFace
 Font.Charset = DEFAULT_CHARSET
 Font.Color = clWindowText
 Font.Height = -11
 Font.Name = 'Tahoma'
 Font.Style = []
 OldCreateOrder = False
 PixelsPerInch = 96
 TextHeight = 13
 object Button1: TButton
  Left = 64
  Top = 48
  Width = 75
  Height = 25
  Caption = 'Button1'
  TabOrder = 0
  OnClick = Button1Click
 end
end

  在上面例子中, 每點一次鼠標, 那個回調函數才執行一次; 作為定時器, 如果想讓它每秒執行一次怎麼弄?

  但每一次執行那個 APC 函數, 都得有 SleepEx(當然不止它)給送進去, 那這樣得反復調用 SleepEx 才可以.

  怎麼調用, 用循環嗎? 別說網上能找到的例子我沒見到不用循環的(太笨了), 就在那個 APC 函數裡調用不就完了.

  當然這時一般要設時間間隔的, 下面我們將設間隔為 1000(1秒).

  但接著問題又來了, 譬如把代碼修改成:

var
 hTimer: THandle;
procedure TimerAPCProc(lpArgToCompletionRoutine: Pointer; dwTimerLowValue: DWord;
 dwTimerHighValue: DWord); stdcall;
begin
 Form1.Text := IntToStr(StrToIntDef(Form1.Text, 0) + 1);
 SleepEx(INFINITE, True); {這裡再次調用 SleepEx}
end;
procedure TForm1.Button1Click(Sender: TObject);
var
 DueTime: Int64;
begin
 hTimer := CreateWaitableTimer(nil, True, nil);
 DueTime := 0;
 {下面的參數 1000 表示間隔 1秒}
 if SetWaitableTimer(hTimer, DueTime, 1000, @TimerAPCProc, nil, False) then
 begin
  SleepEx(INFINITE, True);
 end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
 CloseHandle(hTimer);
end;


 

任務能完成, 但窗體"死"了... 怎麼辦? 嘿, 現在學的不是多線程嗎?

  下面例子中, 同時使用了 CancelWaitableTimer 來取消定時器, 很好理解; 效果圖:

  多線程編程(16) - 多線程同步之 WaitableTimer (等待定時器對象)[續二]

  代碼文件:

unit Unit1;
interface
uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, ExtCtrls, StdCtrls;
type
 TForm1 = class(TForm)
  Button1: TButton;
  Button2: TButton;
  procedure Button1Click(Sender: TObject);
  procedure Button2Click(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
 end;
var
 Form1: TForm1;
implementation
{$R *.dfm}
var
 hTimer: THandle;
{APC 函數}
procedure TimerAPCProc(lpArgToCompletionRoutine: Pointer; dwTimerLowValue: DWord;
 dwTimerHighValue: DWord); stdcall;
begin
 Form1.Text := IntToStr(StrToIntDef(Form1.Text, 0) + 1);
 SleepEx(INFINITE, True);
end;
{線程入口函數}
function MyThreadFun(p: Pointer): Integer; stdcall;
var
 DueTime: Int64;
begin
 DueTime := 0;
 {SetWaitableTimer 必須與 SleepEx 在同一線程}
 if SetWaitableTimer(hTimer, DueTime, 1000, @TimerAPCProc, nil, False) then
 begin
  SleepEx(INFINITE, True);
 end;
 Result := 0;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
 ID: DWord;
begin
 {建立 WaitableTimer 對象}
 if hTimer = 0 then hTimer := CreateWaitableTimer(nil, True, nil);
 CreateThread(nil, 0, @MyThreadFun, nil, 0, ID); {建立線程}
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
 CancelWaitableTimer(hTimer); {取消定時器}
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
 CloseHandle(hTimer);
end;
end.

窗體文件:

object Form1: TForm1
 Left = 0
 Top = 0
 Caption = 'Form1'
 ClIEntHeight = 113
 ClIEntWidth = 203
 Color = clBtnFace
 Font.Charset = DEFAULT_CHARSET
 Font.Color = clWindowText
 Font.Height = -11
 Font.Name = 'Tahoma'
 Font.Style = []
 OldCreateOrder = False
 PixelsPerInch = 96
 TextHeight = 13
 object Button1: TButton
  Left = 55
  Top = 32
  Width = 97
  Height = 25
  Caption = #21551#21160#23450#26102#22120
  TabOrder = 0
  OnClick = Button1Click
 end
 object Button2: TButton
  Left = 55
  Top = 63
  Width = 97
  Height = 25
  Caption = #21462#28040#23450#26102#22120
  TabOrder = 1
  OnClick = Button2Click
 end
end
  使用 APC 回調函數才是 WaitableTimer 的正途, 下次該是如何給這個函數傳遞參數了.

 


 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved