●Win32API(C言語)編 第53章 イベントオブジェクトを使う

'2006/12/27 VisualC++2005 ExpressEdition への対応。

○イベントオブジェクト

前章でOVERLAPPED構造体を使った非同期I/Oの方法を説明しました。前章では触れずに 終わりましたが、OVERLAPPED構造体のhEventメンバを使用すると、非同期I/Oが完了したことを知ることが可能になり ます。このhEventメンバに指定するのが、イベントオブジェクトのハンドルです。

イベントオブジェクトは、シグナル状態非シグナル状態 という2つの状態を持ちます。それぞれがどんな状態であるかは、状況・使い方によって異なりますが、とにかく どちらかの状態しかありません。いわゆるON/OFF式のスイッチのようなものです。

イベントオブジェクトは、元々、2つ以上の処理が同時進行しているとき、お互いの処理の同期を取るために 使用されるものです。そのため同期イベントという呼び方をします。何か処理が 終わったら、イベントオブジェクトをシグナル状態にすることで相手に伝える、というような使い方です。相手は、 必要に応じて、イベントオブジェクトの状態を調べ、シグナル状態になっていれば完了しているのだと判断できます。 このように非常に単純な仕組みです。

また、状態をシグナル状態にすることをセット、非シグナル状態にすることを リセットと呼びます。セットやリセットは、プログラマが特定のAPI関数を呼び出す だけで簡単に行えますが、自動リセットというものも存在します。

○イベントオブジェクトを作成する

イベントオブジェクトを使うためには、まず作成しなければなりません。イベントオブジェクトを作成するには、 CreateEvent()を使います。

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);

第1引数は、セキュリティ属性の指定です。取得したハンドルを、子プロセスが継承して使用できるかといった ことですが、とりあえずNULLで構いません。第2引数は、リセットを手動で行うかどうかで、手動リセットにする ならTRUEを指定し、自動リセットにするならFALSEを指定します。第3引数は、初期状態の指定です。TRUEを指定 するとシグナル状態、FALSEを指定すると非シグナル状態で作成されます。第4引数は、イベントオブジェクトの 名前です。名前が不要ならNULLで構いません。この名前は、別のプロセス(つまり別のアプリケーション)との間 で、同じイベントオブジェクトを共有する場合に必要になります。

戻り値として作成されたイベントオブジェクトのハンドルが返されます。失敗した場合はNULLが返ります。なお、 戻り値の型はHANDLE型ですから、最後にはCloseHandle()でクローズしなければなりません。

○非同期I/Oのために使う

一例としてReadFile()するところまでをソース化してみます。

HANDLE hEvent;
HANDLE hFile;
OVERLAPPED ol;
BYTE buf[1000];
DWORD readSize;

// イベントオブジェクトを作成する
hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
if( hEvent == NULL )
{
	// エラー
}

// ファイルを開く
hFile = CreateFile( _T("test.dat"), GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, NULL );
if( hFile != INVALID_HANDLE_VALUE )
{
	ZeroMemory( &ol, sizeof(ol) );
	ol.offset = 0;
	ol.offsetHigh = 0;
	ol.hEvent = hEvent;
	if( ReadFile( hFile, buf, sizeof(buf), &readSize, &ol ) )
	{
		// 読み込み完了
	}
	else
	{
		if( GetLastError() != ERROR_IO_PENDING )
		{
			// エラー
		}
	}
}

単に、最初にCreateEvent()が追加され、それをOVERLAPPED構造体に渡すだけです。ここには登場していません が、最後には忘れずCloseHandle()でイベントオブジェクトハンドルをクローズします。

さて、この状態で非同期I/Oを開始させれば、完了時に、このイベントオブジェクトがシグナル状態に変化します。 あと必要なのは、シグナル状態に変化するまで待つ方法だけです。イベントオブジェクトのような同期を取るメカニズム では、WaitForSingleObject()WaitForMultipleObject() という関数を使って、シグナル状態になるのを待ちます。後者は複数の同期オブジェクトを使用する、より複雑な 処理で使用するものです。

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
DWORD WaitForMultipleObject(DWORD nCount, CONST HANDLE *lpHandles, BOOL fWaitAll, DWORD dwMilliseconds);

WaitForSingleObject()の場合は、第1引数にイベントハンドル、第2引数にタイムアウト時間 を指定します。タイムアウト時間というのは、要するに「これ以上は待たない」という時間のことで、この時間だけ 経過してもシグナル状態にならないようなら、関数から戻ってきます。ここではミリ秒単位で指定を行いますが、 永遠に待ち続けるならINFINITEを指定します。

WaitForMultipleObject()の場合は、第1引数に何個の同期オブジェクトを使うのか指定します。実際のハンドル は配列にまとめて格納しておき、そのアドレスを第2引数に指定します。第3引数は条件指定です。ここがTRUEの 場合は、全てのオブジェクトがシグナル状態になるまで待ち、FALSEの場合は1つでもシグナル状態になったら終了 します。第4引数はWaitForSingleObject()と同じくタイムアウト時間の指定です。

戻り値は、タイムアウトで返ってきた可能性もあるため、数パターン存在します。以下のいずれかが返されます。

意味
WAIT_OBJECT_0シグナル状態になった
WAIT_TIMEOUTタイムアウトによって戻ってきた
WAIT_ABANDONED指定したオブジェクトは既に別スレッドが破棄している
WAIT_FAILED関数が失敗

WaitForMultipleObject()の場合は、上の表よりも更に細かくなります。WAIT_OBJECT_0という名前から予想が 付くように、末尾の数値が何番目のオブジェクトかを現しています。WaitForMultipleObject()の第1引数で 指定した個数 - 1まで末尾の数値が変化し得ます。同様に、WAIT_ABANDONEDもWaitForMultipleObject()の場合は、 末尾に数値が付きます。第3引数の指定で、全てのオブジェクトがシグナル状態になるのを待つように設定して いる場合、末尾の数値としてどれが返されるか分からないので、範囲で確認するようにしなければなりません。


ということで、非同期I/Oの完了を待つには、

if( WaitForSingleObject( hEvent, INFINITE ) == WAIT_OBJECT_0 )
{
	// 完了
}

ということになります。実は、前章で登場したGetOverlappedResult()で、第4引数 をTRUEにした場合、内部では上のような処理が実行されています。結果的にGetOverlappedResult()を使う方法と 比べて、違いはありません。それでもイベントオブジェクトを使う方法が用意されているのは、同じファイルに 対して、同時に複数の非同期I/Oを行うような場合に、それぞれの状態を個別に管理できるようにするためです。 逆に言えば、そういう状況下でなければ、この章で見たような方法を取る理由はなく、GetOverlappedResult() だけで問題はないはずです。

○状態のセットとリセット

せっかくなので、イベントオブジェクトの状態をセット・リセットする方法を紹介しておきます。これは 単純にAPI関数を呼ぶだけで終了します。シグナル状態にするにはSetEvent()、 非シグナル状態にするにはResetEvent()を使います。

BOOL SetEvent(HANDLE hHandle);
BOOL ResetEvent(HANDLE hHandle);

引数が対象のハンドルです。成功すればTRUE、失敗するとFALSEが返されます。ちなみに、自動リセットに よって制御するように、作成したイベントの場合、ResetEvent()は使えません。自動リセットは、スレッドと 共に利用する場合に使用します。




今回重要なのは、非同期I/Oの方法よりも、イベントオブジェクトに関する知識の方です。イベントオブジェクト は、スレッドという概念を利用する高度なプログラミングを行う際、基礎となる ものです。同期を取る方法はイベントオブジェクトだけではありませんが、考え方としてはそれほど変わるものでは ありません。


Win32API(C言語)編のトップページに戻る

サイトのトップページに戻る