タスクが完了するまで待つ
スレッドの調整:インデックス への移動
特定のスレッドの実行が終了するのを待つのではなく,ある処理が完了するのを待つ必要がある場合があります。それには,イベントオブジェクトを使用します。Event オブジェクト(System.SyncObjs.TEvent)は,すべてのスレッドから見えるシグナルのように動作するように,グローバルスコープで作成する必要があります。
ほかのスレッドが依存している操作をスレッドが完了したら,TEvent.SetEvent を呼び出します。SetEvent はシグナルをオンにします。ほかのスレッドは,このシグナルをチェックして処理が完了したことを知ります。シグナルをオフにするには,ResetEvent メソッドを使用します。
たとえば,単一のスレッドではなく複数のスレッドが実行を完了するのを待たなければならない状態を考えてみましょう。どのスレッドが最後に終了するかがわからないため,スレッドのいずれかの WaitFor メソッドを単純に使用することはできません。そのかわりに,各スレッドが終了したときにカウンタをインクリメントさせ,最後のスレッドにイベントを設定させてすべての処理が終了したことを示すシグナルを出させることができます。
次のコードは,完了しなければならないすべてのスレッド用の Classes.OnTerminate イベントハンドラの例を示します。CounterGuard は,グローバルなクリティカルセクションオブジェクトであり,複数のスレッドが同時にカウンタを使用するのを防ぎます。Counter は,完了したスレッドの数をカウントするグローバル変数です。
procedure TDataModule.TaskThreadTerminate(Sender: TObject);
begin
...
CounterGuard.Acquire; { カウンタのためのロックを取得する }
Dec(Counter); { グローバルのカウンタ変数をデクリメントする }
if Counter = 0 then
Event1.SetEvent; { 最後のスレッドの場合にシグナルを出す }
CounterGuard.Release; { カウンタのためのロックを解放する }
...
end;
void __fastcall TDataModule::TaskThreadTerminate(TObject *Sender)
{
...
CounterGuard->Acquire(); // カウンタをロックする
if (--Counter == 0) // グローバルカウンタをデクリメントする
Event1->SetEvent(); // 最後のスレッドの場合にシグナルを出す
CounterGuard->Release(); // カウンタのためのロックを解放する
}
メインスレッドは,Counter 変数を初期化し,タスクスレッドを起動して,WaitFor メソッドを呼び出してすべてが終了したことを知らせるシグナルを待ちます。WaitFor はシグナルが設定されるまで一定の時間待ちます。そして,次の表に示す値のいずれかを返します。
TEvent.WaitFor の戻り値 :
値 | 意味 |
---|---|
wrSignaled |
イベントのシグナルが設定された。 |
wrTimeout |
シグナルが設定されずに指定された時間が経過した。 |
wrAbandoned |
タイムアウトの時間が経過する前に,イベントオブジェクトが破棄された。 |
wrError |
待機中にエラーが発生した。 |
次のコードは,メインスレッドがどのようにタスクスレッドを起動し,すべてが完了したときにどのように再開するかを示します。
Event1.ResetEvent; { スレッドを起動する前にイベントをクリアする }
for i := 1 to Counter do
TaskThread.Create(False); { タスクスレッドを作成し,起動する }
if Event1.WaitFor(20000) <> wrSignaled then
raise Exception;
{ すべてのタスクスレッドが終了し,メインスレッドでの実行を継続する }
Event1->ResetEvent(); // スレッドを起動する前にイベントをクリアする
for (int i = 0; i < Counter; i++)
new TaskThread(false); // タスクスレッドを作成し,起動する
if (Event1->WaitFor(20000) != wrSignaled)
throw Exception;
// すべてのタスクスレッドが終了し,メインスレッドの実行を継続する
メモ: 指定した時間が経過した後もイベントを待ち続けたい場合は,WaitFor メソッドの引数の値に INFINITE を渡します。INFINITE 使用する場合には注意が必要です。期待したシグナルが受け取れないと,スレッドがハングアップします。