Windows內核對象 - 通過異步程序調用(APC)實現的定時功能
1. Windows 定時器內核對象
定時器是一個在特定時間或者規則間隔被激發的內核對象。結合定時器的異步程序調用可以允許回調函數在任何定時器被激發的時候執行。
使用本定時器時,你需要把常量_WIN32_WINNT定義爲0x0400
通過調用CreateWaitableTimer()可以創建一個定時器,此函數返回一個指向內核對象的句柄。若定時器已經存在,你可以通過使用OpenWaitableTimer()獲得一個進程相關的句柄。無論是通過CreateWaitableTimer()還是通過OpenWaitableTimer()獲得的句柄,在不需要定時器時必須釋放,方法是使用函數CloseHandle()。
定時的時間通過調用SetWaitableTimer()來設置,可以設置爲一個特定的時刻(如December 16, 1999 at 9:45 PM)或者一個相對的時間(如從現在起每五分鐘)。函數SetWaitableTime()定時的時間參數要求LARGE_INTEGER類型。這個值應該符合在結構體FILETIME中描述的格式。如果值是正的,代表一個特定的時刻。如果值是負的,代表以100納秒爲單位的相對時間。
你也可以將定時器設置爲週期性的自我激發,方法是向SetWaitableTimer()的第三個參數傳遞一個週期參數(以毫秒爲單位)。在CreateWaitableTimer()的第二個參數傳遞FALSE可以產生一個自動歸零的定時器。
當設置了定時器之後,你就可以將APC與其結合起來。這裏把APC函數稱作完全例程。完全例程的地址作爲SetWaitableTimer()的第四個參數。第五個參數是一個空類型的指針,你可以使用它來傳遞完全例程的參數。
在所有的APC中,要執行一個完全例程則線程必須處於監聽狀態。完全例程將總是被調用SetWaitableTimer()的相同的線程執行,所以此線程必須將必須其自身置於監聽狀態。可以調用下面的任何一個監聽函數來完成監聽狀態的設置:
SleepEx();
WaitForSingleObjectEx();
WaitForMultipleObjectsEx();
MsgWaitForMultipleObjectsEx();
SignalObjectAndWait();
任何一個線程都有一個APC隊列。在調用上面的任何一個函數時,如果線程的APC隊列中有實體,則此線程不會進入休眠狀態,取而代之要做的是將實體從APC隊列中取出,然後調用相應的完全例程。
如果在APC隊列中不存在實體,那麼線程將會被掛起,直至等待條件滿足爲止。滿足等待條件的有:一個實體加入到APC隊列中,超時,激活句柄等,以及在調用MsgWaitForMultipleObjectsEx()情況下,一個消息進入到線程的一個消息隊列中。若等待條件滿足的是APC隊列中的一個實體,那麼線程會被激活,並且執行完全例程,這種情況下的函數的返回值是 WAIT_IO_COMPLETION.
注意點:(1) 在執行完一個完全例程之後,系統會檢查在APC中剩下的實體以處理。一個監視函數僅僅在處理完所有APC實體後才返回。因此,如果實體加入到APC隊列的速度比處理的更快的話,則調用這些函數可能永遠也不能返回。特別當定時等待的時間比起要求執行完全例程的時間更短的話,這種情況更容易發生。
(2) 當使用APC來實現定時器時,設置定時的線程不應該等待定時器的句柄。如果等待定時器的句柄的話,則喚起這個線程的原因是定時器被激活,而不是有實體加入到APC隊列中。這時線程將不再處於監聽狀態,所以完全例程也不會被調用。下面的例中,SleepEx()被用於將線程置於監聽狀態。在定時器激活後,如果有實體被加入到此線程的APC隊列中時,SleepEx()就會喚醒此線程。
【示例代碼】
#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, // Data valueDWORD dwTimerLowValue, // Timer low value
DWORD dwTimerHighValue // Timer high value
)
{
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, // Default security attributesFALSE, // Create auto-reset timer
"MyTimer" ) // Name of waitable timer
)
{__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
{
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 );
}
}
2. APC
APC(asynchronous procedure call)。APC類似線程特有的一個隊列,裏面裝的是函數地址。如果一個函數地址被裝入APC,如果這時線程處於待命的等待狀態(alertable wait),那麼這個線程就會被喚醒去調用APC裏的函數;否則,APC裏的函數地址就會被忽略掉。這裏的這個線程指的是調用SetWaitableTimer的線程。如果指定了APC,那麼就不要等待這個waitable timer對象了,因爲APC隊列會喚醒線程的,不需要wait函數。
void SomeFunc()
{
// 創建一個等待定時器(人工重置)
HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
// 當調用SetWaitableTimer時候立刻通知等待定時器
LARGE_INTEGER li = { 0 };
SetWaitableTimer(hTimer, &li, 5000, TimerAPCRoutine, NULL, FALSE);
// 線程進入“待命等待”狀態,並無限期等待
SleepEx(INFINITE, TRUE);
CloseHandle(hTimer); //關閉句柄
}
當所有的APC項都完成,即所有的異步函數都結束之後,等待的函數纔會返回(比如SleepEx函數)。所以,必須確保等待定時器再次變爲已通知之前,異步函數就完成了,這樣,等待定時器的APC排隊速度不會比它的處理速度慢。