Verwenden von wartebaren Timern mit einem asynchronen Prozeduraufruf

Im folgenden Beispiel wird eine APC-Funktion (Asynchronous Procedure Call, asynchroner Prozeduraufruf), die auch als Abschlussroutine bezeichnet wird, einem wartebaren Timer zugeordnet, wenn der Timer festgelegt wird. Die Adresse der Abschlussroutine ist der vierte Parameter der SetWaitableTimer-Funktion. Der fünfte Parameter ist ein void-Zeiger, mit dem Sie Argumente an die Abschlussroutine übergeben können.

Die Abschlussroutine wird vom gleichen Thread ausgeführt, der SetWaitableTimeraufgerufen hat. Dieser Thread muss sich in einem warnungsfähigen Zustand befinden, um die Abschlussroutine auszuführen. Dies wird erreicht, indem die SleepEx-Funktion aufgerufen wird, die eine warnungsfähige Funktion ist.

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 Ruhezustand versetzt. Stattdessen wird der Eintrag aus der APC-Warteschlange entfernt, und die Abschlussroutine wird aufgerufen.

Wenn kein Eintrag in der APC-Warteschlange vorhanden ist, wird der Thread angehalten, bis die Wartezeit erfüllt ist. Die Wartezeit kann erfüllt werden, indem der APC-Warteschlange ein Eintrag hinzugefügt wird, ein Timeout oder ein Handle signalisiert wird. 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 Abschlussroutine ausgeführt wurde, überprüft das System, ob ein anderer Eintrag in der APC-Warteschlange verarbeitet werden soll. Eine warnungsfähige Funktion gibt erst zurück, nachdem alle APC-Einträge verarbeitet wurden. Wenn einträge daher schneller zur APC-Warteschlange hinzugefügt werden, als sie verarbeitet werden können, ist es 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 als die zum Ausführen der Abschlussroutine erforderliche Zeit 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 führen Sie dazu, dass der Thread aktiviert wird, wenn 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 warnungsfähigen Zustand, und die Abschlussroutine wird nicht aufgerufen. Im folgenden Code weckt der Aufruf von SleepEx den Thread, wenn der APC-Warteschlange des Threads ein Eintrag hinzugefügt wird, nachdem der Timer auf den signalisierten Zustand festgelegt wurde.

#define UNICODE 1
#define _UNICODE 1

#include <windows.h>
#include <stdio.h>
#include <tchar.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

{
   // 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