SDK32 : 非同期手続き呼出し (APC) で待機可能タイマーを使用する方法


概要


待機可能タイマーは、ある時間に、あるいは通常の間隔でシグナル状態になるカーネル オブジェクトです。非同期手続き呼出し (APC) を、タイマーがシグナル状態になる時に実行されるコールバック関数として呼び出すために、待機可能タイマーに関連付けできます。この記事のサンプルコードでこの方法を紹介します。

詳細


待機可能タイマーを使う時、定数 _WIN32_WINNT を 0x0400 として定義する必要があります。この定数は、正しい待機可能タイマー関数のプロトタイプを宣言するために、<windows.h> をインクルードする前に定義しなければなりません。


CreateWaitableTimer() を呼び出すことによって待機可能タイマーを生成します。この関数は、カーネル オブジェクトのハンドルを返します。タイマーが既に存在している場合、OpenWaitableTimer() を使ってプロセスに関係したハンドルを取得することができます。ハンドルは、CreateWaitableTimer() か OpenWaitableTimer() から取得したかどうかに関係なく、タイマーが必要なくなった時に解放しなければなりません。これを行うには CloseHandle() を呼び出します。


タイマーは SetWaitableTimer() を呼び出して設定します。タイマーは特定の時間 (たとえば、1999 年 12 月 16 日午後 9:45) とか、相対時間 (たとえば、今からの 5 分後) のように設定することができます。SetWaitableTimer() の時間は LARGE_INTEGER 型で指定します。この値は、FILETIME 構造体によって記述される形式でなければなりません。

正値ならば特定の時間を表します。それが負値ならば 100 ナノ秒単位の相対時間で表します。後述するサンプルコードでは相対時間を使っています。タイマーは、SetWaitableTimer() の呼び出しの 5 秒後にシグナル状態になります。


また、タイマーを周期的にシグナル状態に設定することができます。周期的な値 (ミリ秒単位) を第 3 パラメタとして SetWaitableTimer() に渡すことによってこれを行います。周期的なタイマーが必要な場合、第 2 パラメタを FALSE にして CreateWaitableTimer() に渡すことによって、自動リセットタイマーとしてタイマーを生成しなければなりません。このサンプルは 2 秒周期でタイマーを設定します。


タイマーを設定する時に、非同期手続き呼出し (APC) 関数を待機可能タイマーと関連させることができます。APC 関数は完了ルーチンとも呼ばれます。完了ルーチンのアドレスは、SetWaitableTimer() の第 4 パラメタに指定します。第 5 パラメタは引数を完了ルーチンに渡せるように void ポインタになっています。


すべての APC と同様に、スレッドは完了ルーチンを実行するために告知可能な状態になければなりません。完了ルーチンは、常に SetWaitableTimer() を呼び出した同じスレッドによって実行されます。このように、このスレッドは自分自身を告知可能な状態にしておかなければなりません。以下の告知可能な関数の 1 つを呼び出すことによってそれを行います。
SleepEx()

WaitForSingleObjectEx()

WaitForMultipleObjectsEx()

MsgWaitForMultipleObjectsEx()

SignalObjectAndWait()

各スレッドは APC キューを持ちます。上記の関数の 1 つが呼ばれる時に、スレッドの APC キューにエントリがある場合、スレッドはスリープ状態になりません。その代わり、エントリは APC キューから外されて完了ルーチンが呼ばれます。


エントリが APC キューに存在しない場合、スレッドは待機状態が満たされるまで中断します。待機状態は、APC キューにエントリが追加されたり、タイムアウトしたり、シグナル状態になるハンドルにより、あるいは、MsgWaitForMultipleObjectsEx() の場合には、スレッドのメッセージキューの 1 つにメッセージを追加することによって満たされます。待機状態が APC キューのエントリによって満たされる場合、スレッドが起こされて、完了ルーチンが呼ばれます。この場合、関数の戻り値は WAIT_IO_COMPLETION です。


重要: 完了ルーチンが実行された後、システムは処理のために APC キューの別のエントリをチェックします。告知可能な関数は、すべての APC エントリが処理された後に復帰します。したがって、これらの関数の 1 つの呼び出しは、エントリが処理されるより速く APC キューにそれらが追加される場合、決して復帰しません。

これは、周期が完了ルーチンを実行するために要求された時間より短い場合に、待機可能なタイマーで起こることがあります。


APC で待機可能なタイマーを使う時、タイマーを設定するスレッドは、タイマーのハンドルで待機してはいけません。これをすると、タイマーがシグナル状態になった結果よりも、エントリが APC キューに追加された結果として、スレッドが起こされることになります。この結果、スレッドは告知可能な状態にはなりませんし、完了ルーチンも呼ばれません。以下のサンプルの中では、スレッドを告知可能な状態にするために SleepEx() を使っています。SleepEx() は、タイマーがシグナル状態になった後で、エントリがスレッドの APC キューに追加された時、スレッドを起こします。

サンプルコード

   #define _WIN32_WINNT 0x0400
#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 秒後にシグナル状態にするために、
// 64 ビットの負の整数を作成
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 ); // 重要!!! スレッドは APC を処理するために告知可能な状態になければならない
}

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

}

関連情報


関連情報を参照するには、以下の「サポート技術情報」 (Microsoft Knowledge Base) をクリックしてください。
188768 INFO: Working with the FILETIME Structure
『 Advanced Windows 改訂第 3 版』 Jeffrey Richter 著、15.7 通知機能付き I/O、アスキー出版局