Utilisation de minuteurs d’attente avec un appel de procédure asynchrone

L’exemple suivant associe une fonction d’appel de procédure asynchrone (APC), également appelée routine d’achèvement, à un minuteur d’attente lorsque le minuteur est défini. L’adresse de la routine d’achèvement est le quatrième paramètre de la fonction SetWaitableTimer . Le cinquième paramètre est un pointeur void que vous pouvez utiliser pour passer des arguments à la routine d’achèvement.

La routine d’achèvement est exécutée par le thread qui a appelé SetWaitableTimer. Ce thread doit être dans un état d’alerte pour exécuter la routine d’achèvement. Pour ce faire, il appelle la fonction SleepEx , qui est une fonction pouvant être alertée.

Chaque thread a une file d’attente APC. S’il existe une entrée dans la file d’attente APC du thread au moment où l’une des fonctions pouvant être alertables est appelée, le thread n’est pas mis en veille. Au lieu de cela, l’entrée est supprimée de la file d’attente APC et la routine d’achèvement est appelée.

S’il n’existe aucune entrée dans la file d’attente APC, le thread est suspendu jusqu’à ce que l’attente soit satisfaite. L’attente peut être satisfaite en ajoutant une entrée à la file d’attente APC, par un délai d’expiration ou par un handle signalé. Si l’attente est satisfaite par une entrée dans la file d’attente APC, le thread est réveillé et la routine d’achèvement est appelée. Dans ce cas, la valeur de retour de la fonction est WAIT_IO_COMPLETION.

Une fois la routine d’achèvement exécutée, le système recherche une autre entrée dans la file d’attente APC à traiter. Une fonction pouvant être alertable retourne uniquement une fois que toutes les entrées APC ont été traitées. Par conséquent, si des entrées sont ajoutées à la file d’attente APC plus rapidement qu’elles ne peuvent être traitées, il est possible qu’un appel à une fonction pouvant être alertable ne retourne jamais. Cela est particulièrement possible avec les minuteurs d’attente, si la période est inférieure à la durée nécessaire à l’exécution de la routine d’achèvement.

Lorsque vous utilisez un minuteur d’attente avec un APC, le thread qui définit le minuteur ne doit pas attendre sur le handle du minuteur. En procédant ainsi, vous provoqueriez le réveil du thread à la suite d’un signal du minuteur plutôt qu’à la suite de l’ajout d’une entrée à la file d’attente APC. Par conséquent, le thread n’est plus dans un état alertable et la routine d’achèvement n’est pas appelée. Dans le code suivant, l’appel à SleepEx réveille le thread lorsqu’une entrée est ajoutée à la file d’attente APC du thread après que le minuteur est défini sur l’état signalé.

#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;
}

Utilisation d’objets du minuteur d’attente