비동기 프로시저 호출과 함께 대기 가능한 타이머 사용

다음 예제에서는 타이머가 설정될 때 대기 가능한 타이머와 APC(비동기 프로시저 호출) 함수(완료 루틴이라고도 함)를 연결합니다. 완료 루틴의 주소는 SetWaitableTimer 함수의 네 번째 매개 변수입니다. 다섯 번째 매개 변수는 인수를 완료 루틴에 전달하는 데 사용할 수 있는 void 포인터입니다.

완료 루틴은 SetWaitableTimer라는 동일한 스레드에서 실행됩니다. 완료 루틴을 실행하려면 이 스레드가 경고 가능한 상태여야 합니다. 경고 가능한 함수인 SleepEx 함수를 호출하여 이 작업을 수행합니다.

각 스레드에는 APC 큐가 있습니다. 경고 가능한 함수 중 하나가 호출되는 시점에 스레드의 APC 큐에 항목이 있는 경우 스레드는 절전 모드로 이동되지 않습니다. 대신 항목이 APC 큐에서 제거되고 완료 루틴이 호출됩니다.

APC 큐에 항목이 없으면 대기가 충족될 때까지 스레드가 일시 중단됩니다. APC 큐에 항목을 추가하거나, 시간 제한을 하거나, 신호를 받는 핸들을 통해 대기를 충족할 수 있습니다. APC 큐의 항목에서 대기가 충족되면 스레드가 각성되고 완료 루틴이 호출됩니다. 이 경우 함수의 반환 값은 WAIT_IO_COMPLETION.

완료 루틴이 실행된 후 시스템은 처리할 APC 큐의 다른 항목을 확인합니다. 경고 가능한 함수는 모든 APC 항목이 처리된 후에만 반환됩니다. 따라서 항목을 처리할 수 있는 것보다 더 빠르게 APC 큐에 추가하는 경우 경고 가능한 함수에 대한 호출이 반환되지 않을 수 있습니다. 이는 완료 루틴을 실행하는 데 필요한 시간보다 기간이 짧은 경우 대기 가능한 타이머에서 특히 가능합니다.

APC에서 대기 가능한 타이머를 사용하는 경우 타이머를 설정하는 스레드는 타이머의 핸들에서 기다리지 않아야 합니다. 이렇게 하면 APC 큐에 항목이 추가되는 결과가 아니라 타이머가 신호를 받으므로 스레드가 절전 모드에서 해제됩니다. 따라서 스레드가 더 이상 경고 가능한 상태가 아니며 완료 루틴이 호출되지 않습니다. 다음 코드에서 SleepEx 호출은 타이머가 신호 상태로 설정된 후 스레드의 APC 큐에 항목이 추가될 때 스레드를 해제합니다.

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

대기 가능한 타이머 개체 사용