等待定时器是在某个时间或按规定的间隔时间,发出自己的信号通知的内核对象。它们通常用来在某个时间执行某个操作。
(程序可以定时地做一些工作,而不需要人参与进去。比如每天定时地升级病毒库,定时地下载电影,定时地更新游戏里的人物。要想实现这些功能,就可以使用定时器的API函数CreateWaitableTimer和SetWaitableTimer来实现了,这对API函数创建的时钟是比较精确的,可以达到100倍的10亿分之一秒。)
若要创建等待定时器,只需要调用CreateWaitableTimer函数:
HANDLE CreateWaitableTimer(
PSECURITY_ATTRIBUTES psa,
BOOL fManualReset,
PCTSTR pszName);
与事件的情况一样, fManualReset参数用于指明人工重置的定时器或自动重置的定时器。
当发出人工重置的定时器信号通知时,等待该定时器的所有线程均变为可调度线程。(为TRUE)
当发出自动重置的定时器信号通知时,只有一个等待的线程变为可调度线程。(为FALSE)
psa和pszName这两个参数为内核对象的安全属性 和内核对象的命名。
当然,进程可以获得它自己的与进程相关的现有等待定时器的句柄,方法是调用OpenWaitableTimer函数:
HANDLE OpenWaitableTimer(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
等待定时器对象总是在未通知状态中创建。必须调用SetWaitableTimer函数来告诉定时器你想在何时让它成为已通知状态:
(FangSH 注:一创建就是未通知状态,要用SetWaitableTimer来设置什么时候为通知状态)
BOOL SetWaitableTimer(
HANDLE hTimer,
const LARGE_INTEGER *pDueTime, //第一次时间
LONG lPeriod, //间隔
PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID pvArgToCompletionRoutine,
BOOL fResume);
hTimer参数用于指明你要设置的定时器。
pDueTime和lPeriod两个参数是一道使用的。PDueTimer参数用于指明定时器何时应该第一次报时,而lPeriod参数则用于指明此后定时器应该间隔多长时间报时一次。下面的代码用于将定时器的第一次报时的时间设置在2002年1月1日的下午1点钟,然后每隔6小时报时一次:
// Declare our local variables.
HANDLE hTimer;
SYSTEMTIME st;
FILETIME ftLocal, ftUTC;
LARGE_INTEGER liUTC;
// Create an auto-reset timer.
hTimer =CreateWaitableTimer(NULL, FALSE, NULL);// 为人工重置
// First signaling is at January1, 2002, at 1:00 P.M. (local time).
st.wYear = 2002; // Year
st.wMonth = 1; // January
st.wDayOfWeek = 0; // Ignored
st.wDay = 1; // The first of the month
st.wHour = 13; // 1PM
st.wMinute = 0; // 0 minutes into the hour
st.wSecond = 0; // 0 seconds into the minute
st.wMilliseconds = 0; // 0 milliseconds into the second
SystemTimeToFileTime(&st, &ftLocal); // 转换成本地时间
// Convert local time to UTCtime. 什么是UTC 时间??
LocalFileTimeToFileTime(&ftLocal, &ftUTC);
// Convert FILETIME toLARGE_INTEGER because of different alignment.
liUTC.LowPart = ftUTC.dwLowDateTime;
liUTC.HighPart =ftUTC.dwHighDateTime;
// Set the timer.
SetWaitableTimer(hTimer, &liUTC, 6 * 60 * 60 * 1000,
NULL, NULL, FALSE);
...
代码首先对SYSTEMTIME结构进行初始化,该结构用于指明定时器何时第一次报时(发出信号通知)。将该时间设置为本地时间,即计算机所在时区的正确时间。SetWaitableTimer的第二个参数的原型是个常量LARGE_INTEGER *,因此它不能直接接受SYSTEMTIME结构。但是,FILETIME结构和LARGE_INTEGER结构拥有相同的二进制格式,都包含两个32位的值。因此,我们可以将SYSTETIME结构转换成FILETIME结构。再一个问题是,SetWaitableTimer希望传递给它的时间始终都采用世界协调时(UTC)的时间。调用LocalFileTimeToFileTime函数,就可以很容易地进行时间的转换。
现在,若要使定时器在2002年1月1日下午1点之后每隔6h进行一次报时,我们应该将注意力转向l Period参数。该参数用于指明定时器在初次报时后每隔多长时间(以毫秒为单位)进行一次报时。如果是每隔6 h进行一次报时,那么我传递21 600 000(6 h×每小时60min×每分钟60s×每秒1000ms)。另外,如果给它传递了以前的一个绝对时间,比如1975年1月1日下午1点,那么SetWaitableTimer的运行就不会失败。
如果不设置定时器应该第一次报时的绝对时间,也可以让定时器在一个相对于调用SetWaitableTimer的时间进行报时。只需要在pDueTime参数中传递一个负值。(FangSH注: 相对时间)传递的值必须是以100ns为间隔。由于我们通常并不以100ns的间隔来思考问题,因此我们要说明一下100ns的具体概念:1 s = 1000ms = 10000 00µs=1000000000ns 。
下面的代码用于将定时器设置为在调用SetWaitableTimer函数后5s第一次报时:
// Declare our local variables.
HANDLE hTimer;
LARGE_INTEGER li;
// Create an auto-reset timer.
hTimer =CreateWaitableTimer(NULL, FALSE, NULL);
// Set the timer to go off 5seconds after calling SetWaitableTimer.
// Timer unit is 100-nanoseconds.
const int nTimerUnitsPerSecond =10000000;
// Negate the time so thatSetWaitableTimer knows we
// want relative time instead ofabsolute time.
li.QuadPart = -(5 *nTimerUnitsPerSecond); //负值意味时间是相对的
// Set the timer.
SetWaitableTimer(hTimer, &li,6 * 60 * 60 * 1000,
NULL, NULL, FALSE);
...
通常情况下,你可能想要一个一次报时的定时器,它只是发出一次报时信号,此后再也不发出报时信号。若要做到这一点,只需要为lPeriod参数传递0即可。然后可以调用CloseHandle函数,关闭定时器,或者再次调用SetWaitableTimer函数,重新设置时间,为它规定一个需要遵循的新条件。
SetWaitableTimer的最后一个参数是fResume,它可以用于支持暂停和恢复的计算机。
通常可以为该参数传递FALSE,就像我在上面这个代码段中设置的那样。但是,如果你编写了一个会议安排类型的应用程序,在这个应用程序在中,你想设置一个为用户提醒会议时间安排的定时器,那么应该传递TRUE。当定时器报时的时候,它将使计算机摆脱暂停方式(如果它处于暂停状态的话),并唤醒等待定时器报时的线程。然后该应用程序运行一个波形文件,并显示一个消息框,告诉用户即将举行的会议。如果为fResume参数传递FALSE,定时器对象就变为已通知状态,但是它唤醒的线程必须等到计算机恢复运行(通常由用户将它唤醒)之后才能得到CPU时间。
除了上面介绍的定时器函数外,最后还有一个CancelWaitableTimer函数:
BOOLCancelWaitableTimer(HANDLE hTimer);
这个简单的函数用于取出定时器的句柄并将它撤消,这样,除非接着调用SetWaitableTimer函数以便重新设置定时器,否则定时器决不会进行报时。如果想要改变定时器的报时条件,不必在调用SetWaitableTimer函数之前调用CancelWaitableTimer函数。每次调用SetWaitableTimer函数,都会在设置新的报时条件之前撤消定时器原来的报时条件。
FangSH 2011-01-02