首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

利用非同步程序呼叫 (APC) 實現高精度定時器

     編輯:關於VC++

概述

定時器是在特定時間或固定間隔被觸發的核心物件。結合非同步程序呼叫 (APC),可讓回呼函數在定時器觸發時執行。本文展示完整實作範例。

前置需求

使用前需定義常數 _WIN32_WINNT 為 0x0400,且必須在包含標頭檔前定義,確保宣告正確的定時器原型函數。

核心 API 流程

  1. 建立定時器:CreateWaitableTimer() 回傳核心物件控制代碼。若已存在,可用 OpenWaitableTimer() 取得進程相關控制代碼。無論何種方式取得,不需時必須用 CloseHandle() 釋放。
  2. 設定觸發時間:SetWaitableTimer() 可設定絕對時間(如 1999/12/16 21:45)或相對時間(如每 5 分鐘)。參數需 LARGE_INTEGER 格式,符合 FILETIME 結構。正值表絕對時間,負值表以 100 奈秒為單位的相對時間。範例使用相對時間,每 5 秒觸發一次。
  3. 週期性自動觸發:向 SetWaitableTimer() 第三參數傳遞週期 (毫秒)。CreateWaitableTimer() 第二參數傳 FALSE 建立自動重置定時器。範例設定 2 秒週期。
  4. 結合 APC:APC 函數稱「完成常式」,其位址作為 SetWaitableTimer() 第四參數。第五參數為 void 指標,可傳遞參數。

執行緒必須處於「警覺狀態」

所有 APC 中,要執行完成常式,執行緒必須處於警覺狀態。完成常式總在呼叫 SetWaitableTimer() 的同一執行緒執行,故該執行緒必須自我置於警覺狀態。可呼叫以下任一函數:

  • SleepEx()
  • WaitForSingleObjectEx()
  • WaitForMultipleObjectsEx()
  • MsgWaitForMultipleObjectsEx()
  • SignalObjectAndWait()

每個執行緒擁有 APC 佇列。呼叫上述函數時,若佇列有實體,執行緒不進入睡眠,改為取出實體並呼叫對應完成常式。若無實體,執行緒掛起直到等待條件滿足:APC 佇列新增實體、逾時、觸發控制代碼、或 MsgWaitForMultipleObjectsEx() 情況下訊息佇列有新訊息。若因 APC 實體喚醒,函數回傳 WAIT_IO_COMPLETION。

重要提示

  1. 完成常式執行完畢後,系統檢查 APC 佇列剩餘實體處理。一個等待函數僅在處理完所有 APC 實體後返回。若實體加入速度快於處理速度,函數可能永不返回——特別是定時間隔短於完成常式執行時間時。
  2. 使用 APC 實現定時器時,設定定時器的執行緒不應等待定時器控制代碼。若等待,喚醒原因會是定時器觸發而非 APC 實體,導致執行緒非警覺狀態,完成常式不被呼叫。範例用 Sleep() 置於警覺狀態,定時器觸發後 APC 實體加入佇列即喚醒執行緒。

完整範例代碼

#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <stdio.h>
#define _SECOND 10000000

typedef struct _MYDATA {
    TCHAR *szText;
    DWORD dwValue;
} MYDATA;

VOID CALLBACK TimerAPCProc(
    LPVOID lpArg,           // 資料值
    DWORD dwTimerLowValue,  // 定時器低位值
    DWORD dwTimerHighValue  // 定時器高位值
) {
    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,               // 預設安全屬性
        FALSE,              // 建立自動重置定時器
        "MyTimer" ) )       // 可等待定時器名稱
    {
        __try {
            // 建立整數,從現在起 5 秒後觸發
            qwDueTime = -5 * _SECOND;
            // 複製相對時間到 LARGE_INTEGER
            liDueTime.LowPart = (DWORD)( qwDueTime & 0xFFFFFFFF );
            liDueTime.HighPart = (LONG)( qwDueTime >> 32 );

            bSuccess = SetWaitableTimer(
                hTimer,           // 定時器物件控制代碼
                &liDueTime,       // 觸發時間
                2000,             // 週期 2 秒
                TimerAPCProc,     // 完成常式
                &MyData,          // 完成常式參數
                FALSE );          // 不恢復暫停系統

            if ( bSuccess ) {
                for ( ; MyData.dwValue < 1000; MyData.dwValue += 100 ) {
                    SleepEx( INFINITE, TRUE ); // 無限期等待,進入警覺狀態
                }
            } 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 );
    }
}

原文參考:Using a Waitable Timer with an Asynchronous Procedure Call

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved