HOWTO: Use a Waitable Timer with an Asynchronous Procedure Call
ID: Q184796
|
The information in this article applies to:
-
Microsoft Win32 Application Programming Interface (API), used with:
-
Microsoft Windows NT 4.0
-
Microsoft Windows 2000
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:
Q169088
INFO: Using _WIN32_WINNT with the Platform SDK Header Files
Q188768
INFO: Working with the FILETIME Structure
"Advanced Windows", Third Edition, Jeffrey Richter's, Chapter 15,
"Alertable I/O," Microsoft Press, 1997
Additional query words:
Keywords : kbnokeyword kbAPC kbKernBase kbNTOS400 kbWinOS2000 kbThread kbDSupport kbGrpKernBase kbwin32sdkfaq
Version : winnt:4.0
Platform : winnt
Issue type : kbhowto
|