创建可等待定时器是Windows内部线程同步的方式之一,本文简单讲述如何使用这一内核对象进行线程同步。
//创建事件内核对象,默认未触发状态 HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
CreateWaitableTimer创建完成后内核对象处于未触发状态,需要使用API BOOL WINAPI SetWaitableTimer( __in HANDLE hTimer, __in const LARGE_INTEGER* pDueTime, __in LONG lPeriod, __in PTIMERAPCROUTINE pfnCompletionRoutine, __in LPVOID lpArgToCompletionRoutine, __in BOOL fResume ); 来设置计时器对象的一些属性,pDueTime是第一次触发时间(UTC时间),lPeriod表示在第一次触发后计时器应该 以什么样的频率触发。
void CALLBACK TimerCallback( LPVOID lpArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue ) {//这里处理定时器触发 int i = 0; SetEvent(g_hHelpEvent); //do something }
void WaitableTimerTest() { //创建事件内核对象,默认未触发状态 HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL); //创建事件,设置为未触发状态 g_hHelpEvent = CreateEvent(NULL, TRUE, FALSE, NULL); SYSTEMTIME st; GetLocalTime(&st); //st.wSecond += 40; FILETIME ft; SystemTimeToFileTime(&st, &ft); //转换成UTC时间 FILETIME ftUtc; LocalFileTimeToFileTime(&ft, &ftUtc); LARGE_INTEGER fTime; fTime.LowPart = ftUtc.dwLowDateTime; fTime.HighPart = ftUtc.dwHighDateTime; //当前时间的后一分钟触发,以后每10秒钟触发一次 BOOL bRet = SetWaitableTimer(hTimer, &fTime, 10*1000, TimerCallback, (LPVOID)hTimer, FALSE); DWORD dwError = GetLastError(); //将TimerCallback回调加入到系统APC队列后,只能通过SleepEx、WaitForSingleObjectEx、WaitForMultipObjectEx、 //或者SignalObjectAndWait 进入等待状态APC回调函数才能被同一线程调用。 while( WAIT_TIMEOUT == WaitForSingleObjectEx(g_hHelpEvent, 1000, FALSE) ) SleepEx(1000*60, TRUE); switch( dwRet ) { case WAIT_OBJECT_0:// cout<<"定时器对象已经触发"<<endl; break; case WAIT_TIMEOUT: cout<<"等待超时,自动终止定时器对象"<<endl; CancelWaitableTimer(hTimer); break; case WAIT_FAILED: cout<<"WaitForSingleObject调用失败,系统错误码:%u"<<GetLastError()<<endl; break; } CloseHandle(hTimer); CloseHandle(g_hHelpEvent); }
//将TimerCallback回调加入到系统APC队列后,只能通过SleepEx、WaitForSingleObjectEx、WaitForMultipObjectEx、
//或者SignalObjectAndWait 进入等待状态APC回调函数才能被同一线程调用。也就是说只能使用这几个API阻塞当前线程,APC回调函数才会被改线程调用。
如果该参数为FALSE,函数不会返回直到超时已到。如果一个I/O回调函数出现,该函数也不会返回而且回调函数也不会执行。如果一个APC函数插入线程,该函数不会返回而且APC函数也不会执行。</span>
如果该参数为TRUE而且SleepEx与扩展I/O函数(ReadFileEx or WriteFileEx)是在同一个线程,函数就会立即返回当线程休眠超时或I/O回调函数出现。如果I/O回调函数出现,那么I/O回调函数会被调用。如果APC被插入线程,该函数不论当前线程是否超时都会立即执行,而且APC函数也会被调用。
BOOL bRet = SetWaitableTimer(hTimer, &fTime, 10*1000, TimerCallback, (LPVOID)hTimer, FALSE);
WaitForSingleObjectEx(hTimer, INFINITE, TRUE);不应该这样来做,因为WaitForSingleObjectEx实际上会等待两次,一次是可提醒,另一次是内核对象句柄。当计时器触发时,等待成功,线程被唤醒,使得线程退出可提醒状态,APC函数不会被调用。
1、定时器消息需要在应用程序中使用大量的用户界面基础设施,从而消耗更多的资源;可等待定时器是内核对象,不仅可以在多个线程间共享,而且具备安全性。
2、定时器消息只能针对一个线程,消息循环是以线程为单位的;可等待定时器则能改变多个线程的可调度状态。
3、WIM_TIMER消息总是优先级最低的,只有在线程的消息队列中没有其他消息了才会被处理。可等待计时器与其他内核对象没有什么不同,如果计时器触发而且线程正在处于等待状态,那么系统将唤醒线程。
需要注意的是,APC回调必须尽快处理完,以免发生一个回调还未处理完成另一个回调又来到了。