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 必须在同一个线程才可以.本例效果图:
代码文件:
unit Unit1;interfaceuses 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 endend在上面例子中, 每点一次鼠标, 那个回调函数才执行一次; 作为定时器, 如果想让它每秒执行一次怎么弄? 但每一次执行那个 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 来取消定时器, 很好理解; 效果图:
代码文件:
unit Unit1;interfaceuses 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 endend使用 APC 回调函数才是 WaitableTimer 的正途, 下次该是如何给这个函数传递参数了.