<8> イベント (同期オブジェクト) (Delphi コンカレントプログラミング)

# 8. イベント (同期オブジェクト)

**イベント (Event)** は同期オブジェクトの一つです。待機中の任意のスレッドのアクセスを許可します。

## 8.1. TEvent

`TEvent` クラス [^1] は `System.SyncObjs` で定義されています。

最初の書式のコンストラクタでは、名前付きのイベントを作成します (名前が空でない場合はシステムイベント)。`ManualReset` パラメータが True だと、手動イベントとなり、`ResetEvent()` メソッドを実行しないと非シグナル状態になりません。False だと自動リセットとなり、`WaitFor()` メソッドで待機しているスレッドが処理を始めると非シグナル状態になります。`InitialState` プロパティはシグナルの初期状態です。True でシグナル状態、False で非シグナル状態です。

二番目の書式のコンストラクタを使うと、無名イベントを作成します。無名イベントはローカルイベントです。手動リセット、非シグナル状態で作成されます。

```pascal
constructor Create(EventAttributes: PSecurityAttributes; ManualReset, InitialState: Boolean; const Name: string; UseCOMWait: Boolean = False);
constructor Create(UseCOMWait: Boolean = False);
```

最初期のこのクラスは Windows API をラップしたものでした。

| Delphi | Windows API |
|:---|:---|
| [TEvent.Create()](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TEvent.Create) コンストラクタ | [CreateEventA()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa) / [CreateEventW()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventw)|
| [TEvent.WaitFor](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.THandleObject.WaitFor) プロパティ| [WaitForSingleObject()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject) |
| [TEvent.SetEvent()](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Resume) メソッド | [SetEvent()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setevent) |
| [TEvent.ResetEvent()](https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.Classes.TThread.Suspend) メソッド | [ResetEvent()](https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-resetevent) |

`WaitFor()` メソッドで待機しているスレッドに対して、`SetEvent()` でイベント (非シグナル -> シグナル) を通知する事ができます。

**See also:**

 - [System.SyncObjs.TEvent (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TEvent)

## 8.1.1. ローカルインスタンスで使う TEvent
イベントは同期オブジェクトなので、通常はグローバルインスタンスとして作成するのですが、`TThread` のフィールドとして使うと面白い事が出来ます。

次のようなユニットを作ります。

```pascal:uEVThread.pas
unit uEVThread;

interface

uses
  System.Classes, System.SyncObjs, Vcl.StdCtrls;

type
  TEvThread = class(TThread)
  private
    FEvent: TEvent;
    FComponent: TComponent;
  protected
    procedure TerminatedSet; override;
  public
    constructor Create(Component: TComponent); overload;
    destructor Destroy; override;
    procedure Stop;
    property  Event: TEvent read FEvent write FEvent;
    property  Component: TComponent read FComponent write FComponent;
  end;

implementation

{ TEvThread }

constructor TEvThread.Create(Component: TComponent);
begin
  FEvent := TEvent.Create;
  FComponent := Component;
  FreeOnTerminate := True;
  inherited Create(True);
end;

destructor TEvThread.Destroy;
begin
  FEvent.Free;
  inherited;
end;

procedure TEvThread.Stop;
begin
  Terminate;
  FEvent.SetEvent;
end;

procedure TEvThread.TerminatedSet;
begin
  inherited;
  FEvent.SetEvent;
end;
end.
```

フォームにメモとボタンが二つあるアプリケーションを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/ea2cc8f4-4a3e-eb5a-b05f-0e659c1b93e1.png)

```pascal
uses
  ..., uEvThread;

type
  TMyThread = class(TEvThread)
  protected
    procedure Execute; override;
  end;
...

{ TMyThread }

procedure TMyThread.Execute;
begin
  inherited;
  if not Terminated then
    begin
      TThread.Sleep(5000);
      Synchronize(
        procedure
        begin
          (Component as TMemo).Lines.Add('Start');
        end
      );
    end;
end;
```

二つのボタンのイベントハンドラはこうなります。

```pascal
// [Start] ボタン
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Clear;
  MyThread := TMyThread.Create(Memo1);
  MyThread.Start;
end;

// [Stop] ボタン
procedure TForm1.Button2Click(Sender: TObject);
begin
  MyThread.Stop; // または MyThread.Terminate;
  Memo1.Lines.Add('Stop');
end;
```

[Start] ボタンを押してすぐに [Stop] ボタンを押しても、`Sleep(5000)` はキャンセルされないため、'Start' は [Start] ボタンを押して 5 秒後に表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/555d3378-a2b0-2e61-18d5-54e8c02836c0.png)

次に `Execute()` メソッド内の `Sleep(5000)` を `Event.WaitFor(5000)` に変更してみます。

```pascal
procedure TMyThread.Execute;
begin
  inherited;
  if not Terminated then
    begin
//    TThread.Sleep(5000);
      Event.WaitFor(5000); // <-
      Synchronize(
        procedure
        begin
          (Component as TMemo).Lines.Add('Start');
        end
      );
    end;
end;
```

今度は [Stop] ボタンを押すとすぐに 'Start' が表示されたと思います。もちろん、'Start' を表示しないようにする事もできます。

```pascal
procedure TMyThread.Execute;
begin
  inherited;
  if not Terminated then
    begin
      Event.WaitFor(5000);
      if not Terminated then // <-
        Synchronize(
          procedure
          begin
            (Component as TMemo).Lines.Add('Start');
          end
        );
    end;
end;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/69732d72-2c82-17f8-6b79-3c49bbbb0ec9.png)

**See also:**

 - [Implement override for thread Terminate method (StackOverflow)](https://stackoverflow.com/questions/33962710/implement-override-for-thread-terminate-method)

## 8.2. TSimpleEvent

`TSimpleEvent` クラス [^2] は `System.SyncObjs` で定義されています。

現在の実装では `TSimpleEvent` クラスと `TEvent` クラス (の無名イベント) に違いはありません [^3] 。以前もコンストラクタのみが異なっていました。

```pascal
constructor TSimpleEvent.Create;
begin
  inherited Create(nil, True, False, '');
end;
```

**See also:**

 - [System.SyncObjs.TSimpleEvent (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TSimpleEvent)

## 8.3. TLightweightEvent

`TLightweightEvent` クラス [^4] は軽量なイベントです。`System.SyncObjs` で定義されています。但し、ローカルイベントとしてしか使えません。

.NET の `ManualResetEventSlim` クラスに相当します。

**See also:**

 - [System.SyncObjs.TLightweightEvent(DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SyncObjs.TLightweightEvent)
 - [ManualResetEventSlim クラス (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.manualreseteventslim)

#参考

 - [排他制御におけるイベント (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)#%E6%8E%92%E4%BB%96%E5%88%B6%E5%BE%A1%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88)

# 索引
[ [← 7. セマフォ (同期オブジェクト)](https://qiita.com/ht_deko/items/6d7aa5ab5856d231b79e) ] [ [↑ 目次へ](https://qiita.com/items/e8c1ff3a4c74e4c2a4f3) ] [ [→ 9. 非同期プログラミング ライブラリ (APL)](https://qiita.com/ht_deko/items/eedf3728be2a301a8a5d) ] 

[^1]: `TEvent` は Delphi 3 以降で利用可能です。
[^2]: `TSimpleEvent` は Delphi 3 以降で利用可能です。
[^3]: `TSimpleEvent` は Delphi 2005 以降、`TEvent` のエイリアスとなっています。
[^4]: `TLightweightEvent` は Delphi XE 以降で利用可能です。