Delphi多线程编程16多线程同步之 WaitableTimer 等待定时器对象续二.docx
《Delphi多线程编程16多线程同步之 WaitableTimer 等待定时器对象续二.docx》由会员分享,可在线阅读,更多相关《Delphi多线程编程16多线程同步之 WaitableTimer 等待定时器对象续二.docx(11页珍藏版)》请在冰豆网上搜索。
Delphi多线程编程16多线程同步之WaitableTimer等待定时器对象续二
想过没有?
WaitableTimer是在"定时等待",前面例子中的WaitForSingleObject等待函数"也在等待",这就"双重等待"了,这不好,太浪费资源.
其实作为同步工具,前面的几种方法(事件、信号、临界区)基本够用了;WaitableTimer的作用并不是为了重复前面的功能,它的主要功用类似TTimer类;譬如每隔多长时间执行一段代码、或在指定的时间去执行一段代码.
既然有了方便的TTimer,何必再使用WaitableTimer呢?
因为WaitableTimer比TTimer精确的多,它的间隔时间可以精确到毫秒、它的指定时间甚至是精确到0.1毫秒;
而TTimer驱动的WM_TIMER消息,是消息队列中优先级最低的,也就是再同一时刻WM_TIMER消息总是被最后处理.
还有重要的一点WaitableTimer可以跨线程、跨进程使用.
继续探讨一个重要的点:
很多时候为了让线程不冲突,线程也在等待,既然有等待,那WaitableTimer非常精确的定时又有什么价值呢?
对这个问题的思考,可以让我们很好地理解APC函数.
SetWaitableTimer有个回调函数(其实是个过程),Windows要求它的格式是:
procedureTimerAPCProc(
lpArgToCompletionRoutine:
Pointer;
dwTimerLowValue:
DWORD;
dwTimerHighValue:
DWORD
);stdcall;
函数名中有APC的字样,指示这是个APC函数(尽管这个名称无所谓,这是官方命名),那什么是APC函数?
APC(AsyncroneusProcedureCall):
异步过程调用.
原来每个线程除了有单独的消息队列,还有一个APC队列(等待执行的APC函数);如果线程发现APC队列中有情况,马上会跳过去执行,执行完毕后才回来接着处理消息队列.
说起来麻烦,使用的时候只按上面格式传入函数指针就行;不过能进入APC队列的回调函数和其他回调函数还有一个很大的不同:
SetWaitableTimer按格式调用APC函数后,需要在"当前线程"见到一个"等待",此APC函数才可以进入队列.
这好像很费解,例说一下:
APC队列有那么高的优先级,因为对资源的优先使用会对其他消息有很大的影响,肯定不能随便进入,这是不是像生活中的贵宾席或贵宾通道?
也就是说,要进入APC队列只有SetWaitableTimer的调用还不够,还要通过"等待函数"介绍一下.
WaitForSingleObject吗?
不是,它不够级别;下面是Windows认可的、可以介绍APC入列的等待函数:
SleepEx();
WaitForSingleObjectEx();
WaitForMultipleObjectsEx();
MsgWaitForMultipleObjectsEx();
SignalObjectAndWait();
为什么是用等待函数来把关?
因为上面几个等待函数也可以等待是否有APC函数想入列.
上面给出的几个等待函数,就SleepEx的参数最少,先用它吧:
functionSleepEx(
dwMilliseconds:
DWORD;{毫秒数}
bAlertable:
BOOL{布尔值}
):
DWORD;stdcall;
//第一个参数和Sleep的那个参数是一样的,是线程等待(或叫挂起)的时间,时间一到不管后面参数如何都会返回.
//第二个参数如果是False,SleepEx将不会关照APC函数是否入列;
//若是True,只要有APC函数申请,SleepEx不管第一个参数如何都会把APC推入队列并随APC函数一起返回.
//注意:
SetWaitableTimer和SleepEx必须在同一个线程才可以.
本例效果图:
代码文件:
unitUnit1;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,
Dialogs,ExtCtrls,StdCtrls;
type
TForm1=class(TForm)
Button1:
TButton;
procedureButton1Click(Sender:
TObject);
procedureFormDestroy(Sender:
TObject);
end;
var
Form1:
TForm1;
implementation
{$R*.dfm}
var
hTimer:
THandle;
{APC函数(过程),函数名和参数名可以不同,格式必须如此}
procedureTimerAPCProc(lpArgToCompletionRoutine:
Pointer;dwTimerLowValue:
DWORD;
dwTimerHighValue:
DWORD);stdcall;
begin
Form1.Text:
=IntToStr(StrToIntDef(Form1.Text,0)+1);{标题+1}
end;
procedureTForm1.Button1Click(Sender:
TObject);
var
DueTime:
Int64;
begin
hTimer:
=CreateWaitableTimer(nil,True,nil);
DueTime:
=0;
ifSetWaitableTimer(hTimer,DueTime,0,@TimerAPCProc,nil,False)then
begin
SleepEx(INFINITE,True);{INFINITE表示一直等}
end;
end;
procedureTForm1.FormDestroy(Sender:
TObject);
begin
CloseHandle(hTimer);
end;
end.
窗体文件:
objectForm1:
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
objectButton1:
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;
procedureTimerAPCProc(lpArgToCompletionRoutine:
Pointer;dwTimerLowValue:
DWORD;
dwTimerHighValue:
DWORD);stdcall;
begin
Form1.Text:
=IntToStr(StrToIntDef(Form1.Text,0)+1);
SleepEx(INFINITE,True);{这里再次调用SleepEx}
end;
procedureTForm1.Button1Click(Sender:
TObject);
var
DueTime:
Int64;
begin
hTimer:
=CreateWaitableTimer(nil,True,nil);
DueTime:
=0;
{下面的参数1000表示间隔1秒}
ifSetWaitableTimer(hTimer,DueTime,1000,@TimerAPCProc,nil,False)then
begin
SleepEx(INFINITE,True);
end;
end;
procedureTForm1.FormDestroy(Sender:
TObject);
begin
CloseHandle(hTimer);
end;
任务能完成,但窗体"死"了...怎么办?
嘿,现在学的不是多线程吗?
下面例子中,同时使用了CancelWaitableTimer来取消定时器,很好理解;效果图:
代码文件:
unitUnit1;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,
Dialogs,ExtCtrls,StdCtrls;
type
TForm1=class(TForm)
Button1:
TButton;
Button2:
TButton;
procedureButton1Click(Sender:
TObject);
procedureButton2Click(Sender:
TObject);
procedureFormDestroy(Sender:
TObject);
end;
var
Form1:
TForm1;
implementation
{$R*.dfm}
var
hTimer:
THandle;
{APC函数}
procedureTimerAPCProc(lpArgToCompletionRoutine:
Pointer;dwTimerLowValue:
DWORD;
dwTimerHighValue:
DWORD);stdcall;
begin
Form1.Text:
=IntToStr(StrToIntDef(Form1.Text,0)+1);
SleepEx(INFINITE,True);
end;
{线程入口函数}
functionMyThreadFun(p:
Pointer):
Integer;stdcall;
var
DueTime:
Int64;
begin
DueTime:
=0;
{SetWaitableTimer必须与SleepEx在同一线程}
ifSetWaitableTimer(hTimer,DueTime,1000,@TimerAPCProc,nil,False)then
begin
SleepEx(INFINITE,True);
end;
Result:
=0;
end;
procedureTForm1.Button1Click(Sender:
TObject);
var
ID:
DWORD;
begin
{建立WaitableTimer对象}
ifhTimer=0thenhTimer:
=CreateWaitableTimer(nil,True,nil);
CreateThread(nil,0,@MyThreadFun,nil,0,ID);{建立线程}
end;
procedureTForm1.Button2Click(Sender:
TObject);
begin
CancelWaitableTimer(hTimer);{取消定时器}
end;
procedureTForm1.FormDestroy(Sender:
TObject);
begin
CloseHandle(hTimer);
end;
end.
窗体文件:
objectForm1:
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
objectButton1:
TButton
Left=55
Top=32
Width=97
Height=25
Caption=#21551#21160#23450#26102#22120
TabOrder=0
OnClick=Button1Click
end
objectButton2:
TButton
Left=55
Top=63
Width=97
Height=25
Caption=#21462#28040#23450#26102#22120
TabOrder=1
OnClick=Button2Click
end
end
使用APC回调函数才是WaitableTimer的正途,下次该是如何给这个函数传递参数了.