How To Use a Waitable Timer with an Asynchronous Procedure Call

Summary

A waitable timer is a kernel object that is signaled at a certain time or at regular intervals. An asynchronous procedure call (APC) can be associated with a waitable timer to allow a callback function to be executed whenever the timer is signaled. The sample code in this article demonstrates how to do this.

More Information

When using waitable timers, you need to define the constant _WIN32_WINNT as 0x0400. This constant should be defined before the inclusion of <windows.h> to ensure that the proper waitable timer function prototypes are declared.


You create a waitable timer by calling CreateWaitableTimer(). This function returns a handle to the kernel object. If the timer already exists, you can obtain a process-relative handle by using OpenWaitableTimer(). The handle, whether obtained through CreateWaitableTimer() or OpenWaitableTimer(), must be released when the timer is no longer needed. Do this by calling CloseHandle().


The timer is set with a call to SetWaitableTimer(). The timer can be set for a specific time (for example, December 16, 1999 at 9:45 PM) or a relative time (for example, 5 minutes from now). SetWaitableTimer() requires a LARGE_INTEGER for the due time. This value should be in the format described by the FILETIME structure. If the value is positive, it represents a specific time. If it is negative, it represents a relative time in 100-nanosecond units. The sample code shown later uses a relative time. The timer will become signaled 5 seconds after the call to SetWaitableTimer().


You can also set the timer to periodically re-signal itself. Do this by passing a periodic value (in milliseconds) as the third parameter to SetWaitableTimer(). If a periodic timer is required, you should create the timer as an auto-reset timer by passing FALSE as the second parameter to CreateWaitableTimer(). This sample sets a timer with a period of 2 seconds.


You can associate an asynchronous procedure call (APC) function with a waitable timer when the timer is set. The APC function is called a completion routine. The address of the completion routine is the fourth parameter to SetWaitableTimer(). The fifth parameter is a void pointer that you can use to pass arguments to the completion routine.


As with all APCs, a thread must be in an alertable state to execute the completion routine. The completion routine will always be executed by the same thread that called SetWaitableTimer(). Thus, this thread must eventually put itself in an alertable state. It accomplishes this by calling one of the following alertable functions:

SleepEx()
WaitForSingleObjectEx()
WaitForMultipleObjectsEx()
MsgWaitForMultipleObjectsEx()
SignalObjectAndWait()
Each thread has an APC queue. If there is an entry in the thread's APC queue at the time that one of the above functions is called, the thread is not put to sleep. Instead, the entry is removed from the APC queue and the completion routine is called.


If no entry exists in the APC queue, the thread is suspended until the wait is satisfied. The wait can be satisfied by adding an entry to the APC queue, by a timeout, by a handle becoming signaled, or, in the case of MsgWaitForMultipleObjectsEx(), by adding a message to one of the thread's message queues. If the wait is satisfied by an entry in the APC queue, the thread is awakened and the completion routine is called. In this case, the return value of the function is WAIT_IO_COMPLETION.


IMPORTANT NOTE: After the completion routine is executed, the system checks for another entry in the APC queue to process. An alertable function will return only after all APC entries have been processed. It is, therefore, possible that a call to one of these functions will never return if entries are being added to the APC queue faster than they can be processed. This is especially possible with waitable timers, where the period is shorter than the amount of time required to execute the completion routine.


When you are using a waitable timer with an APC, the thread that sets the timer SHOULD NOT wait on the handle of the timer. By doing this, you cause the thread to wake up as a result of the timer becoming signaled rather than as the result of an entry being added to the APC queue. As a result, the thread is no longer in an alertable state and the completion routine is not called. In the following sample, SleepEx() is used to put the thread in an alertable state. SleepEx() awakens the thread when an entry is added to the thread's APC queue after the timer becomes signaled.

Sample Code

   #define _WIN32_WINNT 0x0400
#include <windows.h>
#include <stdio.h>

#define _SECOND 10000000

typedef struct _MYDATA {
TCHAR *szText;
DWORD dwValue;
} MYDATA;


VOID CALLBACK TimerAPCProc(
LPVOID lpArg, // Data value.
DWORD dwTimerLowValue, // Timer low value.
DWORD dwTimerHighValue ) { // Timer high value.

MYDATA *pMyData = (MYDATA *)lpArg;

printf( "Message: %s\nValue: %d\n\n", pMyData->szText,
pMyData->dwValue );
MessageBeep(0);

}


void main( void ) {

HANDLE hTimer;
BOOL bSuccess;
__int64 qwDueTime;
LARGE_INTEGER liDueTime;
MYDATA MyData;
TCHAR szError[255];

MyData.szText = "This is my data.";
MyData.dwValue = 100;

if ( hTimer = CreateWaitableTimer(
NULL, // Default security attributes.
FALSE, // Create auto-reset timer.
"MyTimer" ) ) { // Name of waitable timer.

__try {

// Create a negative 64-bit integer that will be used to
// signal the timer 5 seconds from now.
qwDueTime = -5 * _SECOND;

// Copy the relative time into a LARGE_INTEGER.
liDueTime.LowPart = (DWORD) ( qwDueTime & 0xFFFFFFFF );
liDueTime.HighPart = (LONG) ( qwDueTime >> 32 );


bSuccess = SetWaitableTimer(
hTimer, // Handle to the timer object.
&liDueTime, // When timer will become signaled.
2000, // Periodic timer interval of 2 seconds.
TimerAPCProc, // Completion routine.
&MyData, // Argument to the completion routine.
FALSE ); // Do not restore a suspended system.

if ( bSuccess ) {

for ( ; MyData.dwValue < 1000; MyData.dwValue += 100 ) {

SleepEx(
INFINITE, // Wait forever.
TRUE ); // IMPORTANT!!! The thread must be in an
// alertable state to process the APC.
}

} else {
wsprintf( szError, "SetWaitableTimer() failed with Error %d.",
GetLastError() );
MessageBox( NULL, szError, "Error", MB_ICONEXCLAMATION );
}

} __finally {
CloseHandle( hTimer );
}

} else {
wsprintf( szError, "CreateWaitableTimer() failed with Error %d.",
GetLastError() );
MessageBox( NULL, szError, "Error", MB_ICONEXCLAMATION );
}

}

References

For additional information, please see the following article in the Microsoft Knowledge Base:
188768 INFO: Working with the FILETIME Structure
"Advanced Windows", Third Edition, Jeffrey Richter's, Chapter 15, "Alertable I/O," Microsoft Press, 1997
Propiedades

Id. de artículo: 184796 - Última revisión: 11/21/2006 - Revisión: 1

Comentarios