🐥

C++BuilderでTEventを使用してスレッドの一時停止・再開を行う

2023/12/20に公開

1.TThreadのSuspend/Resumeメソッドは非推奨機能

従来のSuspend/Resumeメソッドは、デッドロックや未定義の振る舞いを引き起こす可能性があるため、使用すべきではない。
https://docwiki.embarcadero.com/Libraries/Alexandria/ja/System.Classes.TThread.Resume
https://learn.microsoft.com/ja-jp/windows/win32/procthread/suspending-thread-execution

変わりにTEventやTMutexを使用することが推奨されている。

2.TEventを使用する

単純なサンプルプログラムを作ります。
5秒間操作がなければ"Timeout"を表示し、ボタンをクリックすると"Signal"を表示します。

まず、画面側を作ります。TEventを定義して、ボタンを押すと、TEventがONします。

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "System.SyncObjs.hpp"  // TEvent用
#include "MyThread.h"
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TEvent *event;
MyClass *myclass;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
    event = new TEvent(NULL); // イベントの初期化
    event->ResetEvent();      // イベントOFF状態にする

    myclass = new MyClass(event);  // スレッドを実行
    myclass->Start();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    event->SetEvent();   // イベントON
}

次にスレッド側を実装します。

//---------------------------------------------------------------------------
#include <System.hpp>
#pragma hdrstop

#include "Unit1.h"
#include "MyThread.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------
TEvent *event;  // TEventポインタの定義

__fastcall MyClass::MyClass(TEvent *myevent)
	: TThread(true)
{
    event = myevent;  // メインスレッド側のeventを受け取り
}
//---------------------------------------------------------------------------
void __fastcall MyClass::Execute()
{
    //---- ここにスレッド コードを記述します ----
    while (true){
        // eventがOFFなら最大5秒間停止してelse条件に入る
        // eventがONになると即時if条件に入る
        if ( event->WaitFor(5000) == wrSignaled ) {   // WaitFor(INFINITE)で無限待ち
            Synchronize( NULL, [=](){ Form1->Memo1->Lines->Add("Signal!!"); } );
            event->ResetEvent();
        }else{
            Synchronize( NULL, [=](){ Form1->Memo1->Lines->Add("Timeout!!"); } );
        }
    }
}

3.定期サイクル実行

スレッド側をTTimerのように定期的に実行させるにはいくつか方法がある。

3-1.Sleepを使用する

よくある方法でSleepを使用します。

void __fastcall MyClass::Execute()
{
    while (true){
        Sleep(5000);
	// 次の処理を記述
    }
}

3-2.eventのタイムアウトを利用する

eventをタイムアウトとして利用する。Sleepを使った方が直感的。

void __fastcall MyClass::Execute()
{
    while (true){
        event->WaitFor(5000);   // 5秒停止 ※メインスレッドでeventをONしないことが条件
	// 次の処理を記述
    }
}

3-3.メインスレッド側のタイマでイベントを立てる

メインスレッドのTTimer-OnTimerイベントでSetEvent()を立てることでメインスレッドがトリガーを担当し、処理をスレッドで実行できる。サイクル外での突発的な駆動が可能。

void __fastcall MyClass::Execute()
{
    while (true){
        if ( event->WaitFor(5000) == wrSignaled ) {   // WaitFor(INFINITE)で無限待ち
            event->ResetEvent();    // 即フラグを落とすことで次のトリガーに備える
	    // 以下に処理コードを記述する
	}
    }
}

なかなか奥が深い。

Discussion