本系列意在记录Windwos线程的相关知识点,包括线程基础、线程调度、线程同步、TLS、线程池等。

 

可等待的计时器内核对象

下面的函数CreateWaitableTimer用以创建一个计时器内核对象:

HANDLE WINAPI CreateWaitableTimer(
  __in_opt  LPSECURITY_ATTRIBUTES lpTimerAttributes,
  __in      BOOL bManualReset,
  __in_opt  LPCTSTR lpTimerName
);

第一和第三个参数与内核对象的共有特性有关,与线程同步无关,这里不再阐述,通常传入NULL即可。参数bManualReset表示手动重置(TRUE)还是自动重置(FALSE)。如果是手动重置的话,当内核对象触发时,所有等待的线程都将变为可调度状态;如果是自动重置的话,只有一个线程会被切换到可调度状态。计时器对象被创建的时候总是非触发状态,只有调用下面的SetWaitableTimer才能启动计时器并在设置的时间触发对象:

BOOL WINAPI SetWaitableTimer(
  __in      HANDLE hTimer,
  __in      const LARGE_INTEGER *pDueTime,
  __in      LONG lPeriod,
  __in_opt  PTIMERAPCROUTINE pfnCompletionRoutine,
  __in_opt  LPVOID lpArgToCompletionRoutine,
  __in      BOOL fResume
);
  • hTimer:计时器对象句柄
  • pDueTime:表示计时器第一次触发的时间。详见下面的例子
  • lPeriod:表示计时器第一次触发后,以什么频度触发,详见下面的例子
  • pfnCompletionRoutine:当触发时需要执行的异步过程调用(asynchronous procedure call, APC),感觉在线程同步方面使用不多,此处略过,详情可查看MSDN
  • lpArgToCompletionRoutine:异步过程调用的传入参数
  • fResume是否唤醒休眠的系统

下面这个例子把计时器触发事件设置为2008年1月1日下午1:00,之后每隔6小时触发一次:

HANDLE hTimer;
SYSTEMTIME st;
FILETIME ftLocal,ftUTC;
LARGE_INTEGER liUTC;

hTimer = CreateWaitableTimer(NULL,FALSE,NULL);

st.wYear	= 2008;
st.wMonth	= 1;
st.wDayOfWeek	= 0;
st.wDay		= 1;
st.wHour	= 13;
st.wMinute	= 0;
st.wSecond	= 0;
st,wMilliseconds= 0;

SystemTimeToFileTime(&st,&ftLocal);

LocalFileTimeToFileTime(&ftLocal,&ftUTC);

liUTC.LowPart	= ftUTC.dwLowDateTime;
liUTC.HighPart	= ftUTC.dwHighDateTime;

SetWaitableTimer(hTimer,&liUTC,6*60*60*1000,NULL,NULL,FALSE);


上面的代码利用SYSTEMTIME结构和FILETIME结构来转换LARGE_INTEGER结构。对于SYSTEMTIME,可用SystemTimeToFileTime转化成FILETIME;而可用LocalFileTimeToFileTime把FILETIME转成UTC时间,因为SetWaitableTimer的第二个参数始终认为是UTC时间。FILETIME时间更LARGE_INTEGER具有相同的二进制结构,但是由于FILETIME对齐到32位边界,而LARGE_INTEGER对齐到64位边界,因此,为了避免数据对齐问题,不要直接将FILETIME强制转换成LARGE_INTEGER传入SetWaitableTimer。顺便提一句,LARGE_INTEGER是以100ns为计时粒度,并且它是个联合体,如果在支持64位整数的编译器中,LARGE_INTEGER只有一个QuadPart成员。除了为LARGE_INTEGER传入一个绝对时间,也可以为其传入一个相对时间,比如:

LARGE_INTEGER li;
li.QuadPart = -(5 * 10000000);

lPeriod是以毫秒为单位的间隔时间。如果传入0,计时器只会在指定时间触发一次,不会间隔触发。

 

SetTimer和计时器内核对象

任何有经验的Windows开发人员会立即把计时器内核对象跟用户计时器(通过SetTimer函数来设置)进行比较。两者最大的区别在于用户计时器需要在应用程序中使用大量的用户界面基础设施,从而消耗更多的资源。另外,计时器内核对象可以在多个线程间共享(通过某种共享内核对象的方式),而且可以具备安全性。

用户计时器通过产生WM_TIMER消息,将其送回调用SetTimer线程(对于回调计时器来说),或者创建窗口(对于基于窗口的计时器来说)。因此,当一个用户计时器触发的时候只有一个线程能被通知;而内核对象的触发可以通知多个线程。

如果打算计时器触发时做一些与用户界面相关的操作,使用用户计时器会方便一些。

劳动果实,转载请注明出处:http://www.cnblogs.com/P_Chou/archive/2012/07/07/timer-in-thread-sync.html