Verwenden von wartebaren Timern mit einem asynchronen Prozeduraufruf

Im folgenden Beispiel wird eine APC-Funktion ( asynchrone Prozeduraufruf ), die auch als Abschlussroutine bezeichnet wird, einem wartebaren Timer zugeordnet, wenn der Timer festgelegt ist. Die Adresse der Vervollständigungsroutine ist der vierte Parameter für die SetWaitableTimer-Funktion . Der fünfte Parameter ist ein void-Zeiger, den Sie verwenden können, um Argumente an die Vervollständigungsroutine zu übergeben.

Die Vervollständigungsroutine wird von demselben Thread ausgeführt, der SetWaitableTimer aufgerufen hat. Dieser Thread muss sich in einem warnbaren Zustand befinden, um die Vervollständigungsroutine ausführen zu können. Dies wird erreicht, indem die SleepEx-Funktion aufgerufen wird, bei der es sich um eine warnungsfähige Funktion handelt.

Jeder Thread verfügt über eine APC-Warteschlange. Wenn zum Zeitpunkt des Aufrufs einer der warnungsfähigen Funktionen ein Eintrag in der APC-Warteschlange des Threads vorhanden ist, wird der Thread nicht in den Standbymodus versetzt. Stattdessen wird der Eintrag aus der APC-Warteschlange entfernt, und die Vervollständigungsroutine wird aufgerufen.

Wenn in der APC-Warteschlange kein Eintrag vorhanden ist, wird der Thread angehalten, bis die Wartezeit erfüllt ist. Die Wartezeit kann durch Hinzufügen eines Eintrags zur APC-Warteschlange, durch ein Timeout oder durch ein Handle, das signalisiert wird, erfüllt werden. Wenn die Wartezeit durch einen Eintrag in der APC-Warteschlange erfüllt wird, wird der Thread aktiviert, und die Abschlussroutine wird aufgerufen. In diesem Fall ist der Rückgabewert der Funktion WAIT_IO_COMPLETION.

Nachdem die Vervollständigungsroutine ausgeführt wurde, überprüft das System, ob ein anderer Eintrag in der APC-Warteschlange verarbeitet werden soll. Eine warnungsfähige Funktion wird erst zurückgegeben, nachdem alle APC-Einträge verarbeitet wurden. Wenn Einträge schneller zur APC-Warteschlange hinzugefügt werden, als sie verarbeitet werden können, ist es daher möglich, dass ein Aufruf einer warnungsfähigen Funktion nie zurückgegeben wird. Dies ist insbesondere bei wartebaren Timern möglich, wenn der Zeitraum kürzer ist als die Zeit, die zum Ausführen der Abschlussroutine erforderlich ist.

Wenn Sie einen wartebaren Timer mit einem APC verwenden, sollte der Thread, der den Timer festlegt, nicht auf das Handle des Timers warten. Auf diese Weise würden Sie bewirken, dass der Thread aktiviert wird, weil der Timer signalisiert wird, anstatt als Ergebnis eines Eintrags, der der APC-Warteschlange hinzugefügt wird. Daher befindet sich der Thread nicht mehr in einem warnbaren Zustand, und die Abschlussroutine wird nicht aufgerufen. Im folgenden Code wird der Thread durch den Aufruf von SleepEx aktiviert, wenn der APC-Warteschlange des Threads ein Eintrag hinzugefügt wird, nachdem der Timer auf den signalierten Zustand festgelegt wurde.

#define UNICODE 1
#define _UNICODE 1

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

#define _SECOND 10000000

typedef struct _MYDATA {
   LPCTSTR szText;
   DWORD dwValue;
} MYDATA;

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

{
   // Formal parameters not used in this example.
   UNREFERENCED_PARAMETER(dwTimerLowValue);
   UNREFERENCED_PARAMETER(dwTimerHighValue);

   MYDATA *pMyData = (MYDATA *)lpArg;

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

}

int main( void ) 
{
   HANDLE          hTimer;
   BOOL            bSuccess;
   __int64         qwDueTime;
   LARGE_INTEGER   liDueTime;
   MYDATA          MyData;

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

   hTimer = CreateWaitableTimer(
           NULL,                   // Default security attributes
           FALSE,                  // Create auto-reset timer
           TEXT("MyTimer"));       // Name of waitable timer
   if (hTimer != NULL)
   {
      __try 
      {
         // Create an 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 );       // Put thread in an alertable state
            }

         } 
         else 
         {
            printf("SetWaitableTimer failed with error %d\n", GetLastError());
         }

      } 
      __finally 
      {
         CloseHandle( hTimer );
      }
   } 
   else 
   {
      printf("CreateWaitableTimer failed with error %d\n", GetLastError());
   }

   return 0;
}

Verwenden von wartebaren Timerobjekten