waitable timer

Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“等待定时器”

  等待定时器(waitable timer)是在某个时间或按规定的时间间隔通知自己的内核对象。可以把它理解为一个定时发送信号的东西。

  要创建一个等待定时器内核对象,可以调用函数CreateWaitableTimer。可以为该函数赋予不同的参数来指定一个定时器内核对象的属性。

HANDLE CreateWaitableTimer(
   PSECURITY_ATTRIBUTES psa,
   BOOL bManualReset,
   PCTSTR pszName);

 

  该函数第一个参数是安全属性结构指针。第三个参数是要创建的定时器内核对象名称。第二个参数指明了该定时器内核对象是人工重置(TRUE)的还是自动重置(FALSE)的。该函数成功,返回句柄,失败则返回NULL。

  当一个人工重置的定时器内核对象收到通知时,所有等待在该内核对象上的线程都可以被唤醒,进入就绪状态。一个自动重置的定时器内核对象收到通知时,只有一个等待在该内核对象上的线程可以被调度。

  当然,也可以打开一个特定名字的定时器内核对象,呼叫OpenWaitableTimer函数:

HANDLE OpenWaitableTimer(
   DWORD dwDesiredAccess,
   BOOL bInheritHandle,
   PCTSTR pszName);

 

  等待定时器内核对象创建的时候的状态总是“未通知状态”。你可以呼叫SetWaitableTimer函数来设定等待定时器内核对象何时获得通知。

BOOL SetWaitableTimer(
   HANDLE hTimer,                   
// 等待定时器句柄
    const  LARGE_INTEGER  * pDueTime,    // 第一次通知的时刻(负数表示相对值)
   LONG lPeriod,                     // 以后通知的时间间隔(毫秒)
   PTIMERAPCROUTINE pfnCompletionRoutine,   // APC异步函数地址
   PVOID pvArgToCompletionRoutine,   // APC异步函数参数
   BOOL bResume);                    // 是否让计算机摆脱暂停状态

 

  该函数的第1个参数hTimer是一个等待定时器内核对象的句柄。

  第2个参数pDutTime和第3个参数lPeriod要联合使用,pDutTime是一个LAGRE_INTEGER结构指针,指明了第一次通知的时间,时间格式是UTC(标准时间),是一个绝对值,如果要设置一个相对值,即让等待定时器在调用SetWaitableTimer函数之后多少时间发出第一次通知,只要传递一个负数给该参数即可,但是该数值必须是100ns的倍数,即单位是100ns,下面会举例说明。

  第3个参数指明了以后通知的时间间隔,以毫秒为单位,该参数为0时,表示只有第一次的通知,以后没有通知。

  第4和第5这两个参数与APC(异步过程调用)有关,这里不讨论。

  最后一个参数bResume支持计算机暂停和恢复,一般传递FALSE。当它为TRUE的时候,当定时器通知的时候,如果此时计算机处于暂停状态,它会使计算机脱离暂停状态,并唤醒等待在该等待定时器上的线程。如果它为FALSE,如果此时计算机处于暂停状态,那么当该定时器通知的时候,等待在该等待定时器上的线程会被唤醒,但是要等待计算机恢复运行之后才能得到CPU时间。

 

  比如,下面代码使用等待定时器让它在2008年8月8日晚上8:00开始通知。然后每隔1天通知。 

HANDLE hTimer;      // 等待定时器句柄
SYSTEMTIME st;      // SYSTEMTIME结构,用来设置第1次通知的时间
FILETIME ftLocal, ftUTC;  // FILETIME结构,用来接受STSTEMTIME结构的转换
LARGE_INTEGER liUTC;    // LARGE_INTEGER结构,作为SetWaitableTimer的参数

//  创建一个匿名的默认安全性的人工重置的等待定时器内核对象,并保存句柄
hTimer  =  CreateWaitableTimer(NULL, FALSE, NULL);

// 设置第一次通知时间
st.wYear          =   2008 //  年
st.wMonth         =   8 ;     //  月
st.wDayOfWeek     =   0 ;     //  一周中的某个星期
st.wDay           =   8 ;     //  日
st.wHour          =   20 ;    //  小时(下午8点)
st.wMinute        =   8 ;     //  分
st.wSecond        =   0 ;     //  秒
st.wMilliseconds  =   0 ;     //  毫秒

// 将SYSTIME结构转换为FILETIME结构
SystemTimeToFileTime( & st,  & ftLocal);

// 将本地时间转换为标准时间(UTC),SetWaitableTimer函数接受一个标准时间
LocalFileTimeToFileTime( & ftLocal,  & ftUTC);

//  设置LARGE_INTEGER结构,因为该结构数据要作为SetWaitableTimer的参数
liUTC.LowPart   =  ftUTC.dwLowDateTime;
liUTC.HighPart 
=  ftUTC.dwHighDateTime;

//  设置等待定时器内核对象(一天的毫秒数为24*60*60*1000)
SetWaitableTimer(hTimer,  & liUTC,  24   *   60   *   60   *   1000 ,
                 NULL, NULL, FALSE);

 

  下面的代码创建了一个等待定时器,当调用SetWaitableTimer函数之后2秒会第一次通知,然后每隔1秒通知一次:

HALDLE hTimer;
LARGE_INTEGER li;
hTimer 
=  CreateWaitableTime(NULL, FALSE, NULL);
const   int  nTimerUnitsPerSecond  =   100000000   /   100 // 每1s中有多少个100ns
li.QuadPart  =   - ( 2   *  nTimerUnitsPerSecond );    // 负数,表示相对值2秒
SetWaitableTimer(hTimer,  & li,  1000 , NULL, NULL, FALSE);

 

  当通过SetWaitTimer函数设置了一个等待定时器的属性之后,你可以通过CancelWaitableTimer函数来取消这些设置:

BOOL CancelWaitableTimer(HANDLE hTimer);

 

  当你不再需要等待定时器的时候,通过调用CloseHanble函数关闭之

 

 

等待定时器与APC(异步过程调用)项排队:

 

  Windows允许在等待定时器的通知的时候,那些调用SetWaitTimer函数的线程的异步过程调用(APC)进行排队。

  要使用这个特性,需要在线程调用SetWaitTimer函数的时候,设置第4个参数pfnCompletionRoutine和第5的参数pvArgToCompletionRoutine。这个异步过程需要如下形式:

VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine,
                   DWORD dwTimerLowValue, DWORD dwTimerHighValue)
{
   
//  特定的任务
}

 

  该函数名TimerAPCRoutine可以任意。该函数可以在等待定时器收到通知的时候,由调用SetWaitableTimer函数的线程来调用,但是该线程必须处于“待命等待”状态。也就是说你的线程因为调用以下函数的而处于等待状态中:SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectEx,MsgForMultipleObjectEx,SingleObjectAndWait。如果该线程没有因为调用这些函数而进入等待状态,那么系统不会给定时器APC排队。

 

  下面讲一下详细的APC调用的过程:当你的等待定时器通知的时候,如果你的线程处于“待命等待”状态,那么系统就调用上面具有TimerAPCRoutine异步函数的格式的函数,该异步函数的第一个参数就是你传递给SetWaitableTimer函数的第5个参数pvArgToCompletionRoutine的值。其他两个参数用于指明定时器什么时候发出通知。

  下面的代码指明了使用等待定时器的正确方法:

void  SomeFunc()
{
   
//  创建一个等待定时器(人工重置)
   HANDLE hTimer  =  CreateWaitableTimer(NULL, TRUE, NULL);

   
//  当调用SetWaitableTimer时候立刻通知等待定时器
   LARGE_INTEGER li  =  {  0  };
   SetWaitableTimer(hTimer, 
& li,  5000 , TimerAPCRoutine, NULL, FALSE);

   
//  线程进入“待命等待”状态,并无限期等待
   SleepEx(INFINITE, TRUE);

   CloseHandle(hTimer);   
// 关闭句柄
}

 

  当所有的APC项都完成,即所有的异步函数都结束之后,等待的函数才会返回(比如SleepEx函数)。所以,必须确保等待定时器再次变为已通知之前,异步函数就完成了,这样,等待定时器的APC排队速度不会比它的处理速度慢。

 

  注意,当使用APC机制的时候,线程不能应该等待“等待定时器的句柄”,也不应该以待命等待的方式等待“等待定时的句柄”,下面的方法是错误的:

HANDLE hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

SetWaitableTimer(hTimer, &li, 2000, TimerAPCRoutine, NULL, FALSE);

WaitForSingleObjectEx(hTimer, INFINITE, TRUE);

 

  这段代码让线程2次等待一个等待定时器,一个是等待该等待定时器的句柄,还有一个是“待命等待”。当定时器变为已通知状态的时候,该等待就成功了,然后线程被唤醒,导致线程摆脱了“待命等待”状态,APC函数不会被调用。

 

  由于等待定时器的管理和重新设定是比较麻烦的,所以一般开发者很少使用这个机制,而是使用CreateThreadpoolTimer来创建线程池的定时器来处理问题。

  等待定时器的APC机制也往往被I/O完成端口所替代。

 

  最后,把“等待定时器”和“用户界面定时器”做一下比较。

  用户界面定时器是通过SetTimer函数设置的,定时器一般发送WM_TIMER消息给调用SetTimer函数的线程和窗口,因此只能有一个线程收到通知。而“人工重置”的等待定时器可以让多个线程同时收到通知。

  运用等待定时器,可以让你的线程到了规定的时间就收到通知。而用户界面定时器,发送的WM_TIMER消息属于最低优先级的消息,当线程队列中没有其他消息的时候才会检索该消息,因此可能会有一点延迟。

  另外,WM_TIMER消息的定时精度比较低,没有等待定时器那么高。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 Waitable Timer Objects 实现异步操作的一般步骤如下: 1. 创建一个定时器对象,使用 CreateWaitableTimer() 函数。 2. 使用 SetWaitableTimer() 函数设置定时器的时间间隔。 3. 创建一个事件对象,使用 CreateEvent() 函数。 4. 使用 WaitForMultipleObjects() 函数等待定时器和事件对象。 5. 在等待完成后,处理定时器到期的事件。 下面是一个简单的示例代码,演示如何使用 Waitable Timer Objects 实现异步操作: ```c++ #include <Windows.h> #include <iostream> int main() { // 创建定时器对象 HANDLE hTimer = CreateWaitableTimer(NULL, FALSE, NULL); // 设置定时器的时间间隔为 5 秒 LARGE_INTEGER liDueTime; liDueTime.QuadPart = -50000000LL; // 1 秒 = 10^7 纳秒,因此 5 秒 = 5 * 10^7 纳秒 SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, FALSE); // 创建事件对象 HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // 等待定时器和事件对象 HANDLE waitObjects[2] = { hTimer, hEvent }; DWORD dwWaitResult = WaitForMultipleObjects(2, waitObjects, FALSE, INFINITE); // 处理定时器到期的事件 if (dwWaitResult == WAIT_OBJECT_0) { std::cout << "Timer expired!" << std::endl; } else if (dwWaitResult == WAIT_OBJECT_0 + 1) { std::cout << "Event signalled!" << std::endl; } // 关闭定时器和事件对象 CloseHandle(hEvent); CloseHandle(hTimer); return 0; } ``` 在这个示例中,我们创建了一个定时器对象和一个事件对象。我们使用 SetWaitableTimer() 函数设置定时器的时间间隔为 5 秒。然后,我们使用 WaitForMultipleObjects() 函数等待定时器和事件对象。如果定时器到期,我们打印一条消息。如果事件对象被触发,我们也打印一条消息。最后,我们关闭定时器和事件对象。 这个示例只是一个简单的演示。实际上,使用 Waitable Timer Objects 实现异步操作需要更多的细节和考虑。但是,这个示例可以帮助你了解如何开始使用 Waitable Timer Objects。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值