<1> スレッドオブジェクト (Delphi コンカレントプログラミング)

# 1. スレッドオブジェクト

`TThread` クラスは、アプリケーション内で別の実行スレッド (バックグラウンドスレッド) を作成できるようにする抽象クラスで、`System.Classes` で定義されています。`BeginThread()` と `EndThread()` が内部で使われています。

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

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

| Delphi | Windows API |
|:---|:---|
| [TThread.Create()](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Create) コンストラクタ | [System.BeginThread()](https://docwiki.embarcadero.com/Libraries/ja/System.BeginThread) = [CreateThread()](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread) |
| [TThread.Priority](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Priority) プロパティ| [GetThreadPriority()](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadpriority) / [SetThreadPriority()](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadpriority) |
| [TThread.Resume()](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Resume) メソッド | [ResumeThread()](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread) |
| [TThread.Suspend()](https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.Classes.TThread.Suspend) メソッド | [SuspendThread()](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread) |

最近の Delphi の `TThread` クラスはマルチプラットフォーム対応ですが、`Resume()` / `Suspend()` メソッドは非推奨とマークされている [^1] 上に、Windows / macOS プラットフォーム向けにしか実装されていません。

`BeginThread()` を実行するか、スレッドオブジェクトが生成されると、**IsMultiThread** グローバル変数が True になり、メモリマネージャがマルチスレッドをサポートするようになります。Windows API を直接叩いた場合には IsMultiThread を手動で True に変更する必要があります。

## 1.1. TThread の使い方

`TThread` は抽象クラスなので、派生して (サブクラス化して) 使います。

### 1.1.1. TThread の派生

スレッドオブジェクトを作るためのウィザードは `[ファイル | 新規作成 | その他]` から呼び出せます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/0b5e33c5-6dcc-f6b1-2ece-29edab06b8b3.png)

スレッドクラス名を `TMyThread` にして、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/8a52127c-5cda-12fc-cd86-622f7681256a.png)

自動生成されたユニットが次のようなコードになります。

```pascal:unit2.pas
unit Unit2;

interface

uses
  System.Classes;

type
  TMyThread = class(TThread)
  private
    { Private 宣言 }
  protected
    procedure Execute; override;
  end;

implementation

{ TMyThread }

procedure TMyThread.Execute;
begin
  { スレッドとして実行したいコードをここに記述してください }
end;

end.
```

スレッドとして実行したいコードは `Exceute()` メソッドに記述します。

慣れてくると、ウィザードを使わずにスレッドクラスを書けるようになりますが、`〔Shift〕+〔Ctrl〕+〔C〕`でクラス補完すると `TThread.Execute()` メソッドに **inherited** が追加されてしまいます。`TThread.Execute()` は抽象メソッドなので、**inherited** の記述は不要です。

```pascal
procedure TMyThread.Execute;
begin
  inherited; // 不要
  
end;
```

**See also:**

 - [スレッドオブジェクトの定義 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89_%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E5%AE%9A%E7%BE%A9)

### 1.1.2. スレッドオブジェクトの実行

スレッドクラスはインスタンス化したと同時に実行されます (Execute メソッドが呼ばれます)。

```pascal
    constructor Create; overload;
    constructor Create(CreateSuspended: Boolean); overload;
{$IF Defined(MSWINDOWS)}
    constructor Create(CreateSuspended: Boolean; ReservedStackSize: NativeUInt); overload;
{$ENDIF MSWINDOWS}
```

 - 最初の書式 [^2] はインスタンス化したと同時に Execute メソッドが呼ばれます。
 - 二番目の書式は `CreateSuspended` パラメータを True に設定する事により、スレッドを中断した状態 (サスペンド状態) で作成します。Execute メソッドは `Start()` [^3] (または `Resume()`) メソッドが呼ばれるまで実行されません。
 - 三番目の書式 [^4] は `ReservedStackSize` を指定する事でスタックサイズを設定できますが、Windows 専用です。

`Execute()` メソッドを抜けると、スレッドが終了してしまうので、繰り返し処理したい場合には `Terminated` プロパティをチェックしながらループします。

```pascal
procedure TMyThread.Execute;
begin
  while not Terminated do 
  begin
    ...
  end;
end;
```

:::note warn
古い Delphi ではサスペンド状態で作成したスレッドオブジェクトを Resume() することなく破棄するとメモリリークを起こしていました。
:::

:::note warn
Resume() メソッドはサスペンド状態で作成されたスレッドを開始するためにのみ使うべきです。また、この用途のための Resume() はマルチプラットフォーム対応となっています。
:::

**See also:**

 - [System.Classes.TThread.Create (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Create)
 - [System.Classes.TThread.Start (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Start)

### 1.1.3. FreeOnTerminate

スレッド実行完了時に自動でスレッドオブジェクトを破棄したい場合には、`FreeOnTerminate` プロパティを True に設定します。

```pascal
  MyThread := TMyThread.Create(True);
  MyThread.FreeOnTerminate := True;
  MyThread.Start;
```

`FreeOnTerminate` プロパティを True に設定するタイミングは `Execute()` メソッドが終了する前であればどこでも構わないため、スレッドをサスペンド状態で作成したくないのなら、`Execute()` メソッドの先頭で記述する事もできます。

```pascal
procedure TMyThread.Execute;
begin
  FreeOnTerminate := True;
  ...
end;
```

スレッドオブジェクトは実行後に自動破棄されますが、スレッド実行中にアプリケーションを終了させた場合には普通にメモリリークします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/f8db7ac6-250c-a4ee-f8f0-d3cf2fd71a11.png)

スレッドが実行中の場合には適切な中止処理を行う必要があります。

**See also:**

 - [System.Classes.TThread.FreeOnTerminate (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.FreeOnTerminate)

### 1.1.4. OnTerminate イベント

`TThread` にはスレッドが終了した時に呼ばれる `OnTerminate` イベント (`TNotifyEvent` 型) があります。

```pascal
  MyThread := TMyThread.Create(True);
  MyThread.FreeOnTerminate := True;
  MyThread.OnTerminate := MyThreadTerminate;
  MyThread.Start;
```

`OnTerminate` のイベントハンドラはメインスレッドで実行されます。

**See also:**

 - [System.Classes.TThread.OnTerminate (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.OnTerminate)


### 1.1.5. 明示的なスレッドオブジェクトの破棄

`FreeOnTerminate = False` なスレッドオブジェクトを明示的に破棄するには `FreeAndNil()` を使います。

```pascal
  FreeAndNil(MyThread);
```

丁寧な破棄は次のようになります。

```pascal
  MyThread.Terminate;
  MyThread.WaitFor;
  FreeAndNil(MyThread);
```

結局の所、いきなり `FreeAndNil()` しても上記処理が実行されます。

:::note info
MyThread.Free で破棄するよりも、FreeAndNil() を使って破棄した方がトラブルは少なくなると思います。
:::

### 1.1.6. スレッドオブジェクトの終了 (中止)

#### ・FreeOnTerminate = True なスレッドオブジェクトの終了 (中止)

`Terminate` メソッドを呼びます。

```pascal
  if Assigned(MyThread) then
    MyThread.Terminate;
```

この `Terminate` メソッドは、基本的に `Terminated` プロパティを True に設定しているだけなので、`Execute()` メソッドには (`Terminated` プロパティをチェックしながら) スレッドを正しく終わらせられるコードを記述する必要があります。


#### ・FreeOnTerminate = False なスレッドオブジェクトの終了 (中止)

`FreeAndNil()` で破棄するのが簡単だと思います。

```pascal
  FreeAndNil(MyThread);
```

### 1.1.7. スレッドにパラメータを渡す
普通のクラスと同じようにコンストラクタを定義します。

```pascal
  public
    { public 宣言 }
    constructor Create(Index: Integer);

...

constructor TMyThread.Create(Index: Integer);
begin
  inherited Create(False);
  Self.FreeOnTerminate := True;

  // 処理

end;
```

:::note
サスペンド状態でインスタンス化されたスレッドに対してはプロパティでの受け渡しも可能です。
:::

### 1.1.8. スレッドに名前を付ける

デバッグ用途でスレッドに名前を付けたい場合には、クラスメソッド `NameThreadForDebugging()` を使います [^5]。ウィザードで `名前付きスレッド` にチェックを入れると同等のコードが吐かれます [^6]。

```pascal
procedure TMyThread.Execute;
begin
  NameThreadForDebugging('MyThread1');

  while not Terminated do 
  begin
    ...
  end;
end;
```

古い Delphi のウィザードでは `NameThreadForDebugging()` と同等の `SetName()` メソッドが自動生成されていました。

```pascal
unit Unit2;

interface

uses
  Classes {$IFDEF MSWINDOWS} , Windows {$ENDIF};

type
  TMyThread = class(TThread)
  private
    procedure SetName;
  protected
    procedure Execute; override;
  end;

implementation

{$IFDEF MSWINDOWS}
type
  TThreadNameInfo = record
    FType: LongWord;     // 0x1000 にすること。
    FName: PChar;        // (ユーザー空間での) 名前文字列へのポインタ
    FThreadID: LongWord; // スレッド ID (呼び出しスレッドは -1)
    FFlags: LongWord;    // 将来のために予約。0 にすること。
  end;
{$ENDIF}

{ TMyThread }

procedure TMyThread.SetName;
{$IFDEF MSWINDOWS}
var
  ThreadNameInfo: TThreadNameInfo;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
  ThreadNameInfo.FType := $1000;
  ThreadNameInfo.FName := 'MyThread';
  ThreadNameInfo.FThreadID := $FFFFFFFF;
  ThreadNameInfo.FFlags := 0;

  try
    RaiseException( $406D1388, 0, sizeof(ThreadNameInfo) div sizeof(LongWord), @ThreadNameInfo );
  except
  end;
{$ENDIF}
end;

procedure TMyThread.Execute;
begin
  SetName;
  { ToDo : スレッドとして実行したいコードをこの下に記述してください }
end;

end.
```

**See also:**

 - [スレッドに名前を付ける (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%81%AB%E5%90%8D%E5%89%8D%E3%82%92%E4%BB%98%E3%81%91%E3%82%8B)
 - [System.Classes.TThread.NameThreadForDebugging (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.NameThreadForDebugging)


### 1.1.9. Synchronize() メソッド

VCL / FMX コンポーネントはメインスレッドで動いているので、バックグラウンドスレッドからコンポーネントを更新したい場合には、`Synchronize()` メソッドを使い、パラメータで指定された手続きをメインスレッド内で実行します。

```pascal
procedure TMyThread.Hoge;
begin
  // 何かしらの VCL / FMX コンポーネントを操作する処理
end;

procedure TMyThread.Execute;
begin
  NameThreadForDebugging('MyThread1');

  while not Terminated do 
  begin
    ...
    Synchronize(Hoge);
    ...
  end;
end;
```

指定した手続きがメインスレッド内で実行されている間はバックグラウンドスレッドは停止していますので、頻繁に呼び出すべきではありません。また、`Synchronize()` はパラメータとして

 - 手続きのメソッドポインタ型 (**of object**)
 - 手続きのメソッド参照型 (**reference to**)

を受け付けるため、ここに**無名メソッド**を指定する事ができます [^7]。

```pascal
procedure TMyThread.Execute;
begin
  NameThreadForDebugging('MyThread1');

  while not Terminated do 
  begin
    ...
    Synchronize(
      procedure
      begin
        // 何かしらの VCL / FMX コンポーネントを操作する処理
      end
    );
    ...
  end;
end;
```

:::note warn
Synchronize() はメッセージループを利用するため、コンソールアプリケーションでは使用できません。
:::

:::note warn
Delphi 6 以降、Synchronize() は DLL 内で機能しなくなりました。
:::

**See also:**

 - [System.Classes.TThread.Synchronize (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Synchronize)
 - [Delphi での無名メソッド (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/Delphi_%E3%81%A7%E3%81%AE%E7%84%A1%E5%90%8D%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)
 - [D6DLLSynchronizer for Delphi 6 and 7 (Code Central)](https://cc.embarcadero.com/item/21148)

### 1.1.10. CreateAnonymousThread() メソッド

`TThread` クラスには `CreateAnonymousThread()` というクラスメソッドが用意されています [^8]。手続きのメソッド参照型をパラメータとして受け付けるこのメソッドは、内部的に生成された TThread 派生クラスのインスタンスを返します。スレッドオブジェクトは

 -  サスペンド状態
 -  FreeOnTerminate = True

で作られるため、次のような使い方になります。

```pascal
  TThread.CreateAnonymousThread(
    procedure
    begin
      // 何かしらの処理
    end
  ).Start;
```

`Synchronize()` はどうするのかと言うと、クラスメソッドの方の `Synchronize()` を使います [^9]。

```pascal
  TThread.CreateAnonymousThread(
    procedure
    begin
      // 何かしらの処理

      TThread.Synchronize(TThread.CurrentThread,
        procedure
        begin
          // 何かしらの VCL / FMX コンポーネントを操作する処理
        end);
    end
  ).Start;
```

`OnTerminate` はどうするのかと言うと、一旦変数に取ります。

```pascal
procedure TForm1.MyThreadTerminate(Sender: TObject);
begin
  // 何かしらのスレッド終了時処理
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  var MyThread := TThread.CreateAnonymousThread(
    procedure
    begin
      // 何かしらの処理

      TThread.Synchronize(TThread.CurrentThread,
        procedure
        begin
          // 何かしらの VCL / FMX コンポーネントを操作する処理
        end);
    end
  );
  MyThread.OnTerminate := MyThreadTerminate;
  MyThread.Start;
end;
```

こうなってしまうと、普通に派生クラス (サブクラス) を書いた方が可読性が高そうです。

**See also:**

 - [System.Classes.TThread.CreateAnonymousThread (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.CreateAnonymousThread)
 - [引数を渡せる匿名スレッド (Qiita: @MiraiNagato)](https://qiita.com/MiraiNagato/items/7207d5456dbb83b25272)

### 1.1.11. Queue() メソッド

`Synchronize()` を `Queue()` [^10] で置き換えると、バックグラウンドスレッドがパラメータで指定された手続きの実行を待たなくなります。

ただし、`Queue()` に指定した手続きの呼び出しは、スレッドが終了すると削除されてしまうので注意が必要です。新規に VCL アプリケーションを作って確認してみます。フォームにはメモとボタンが一つずつあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/f25fc1db-a448-5148-d69b-4e654c10758a.png)

ボタンのイベントハンドラに無名スレッドを記述します。

```pascal
procedure TForm1.Button1Click(Sender: TObject);
begin
 TThread.CreateAnonymousThread(
   procedure
   begin
     TThread.Queue(TThread.Current,
                   procedure
                   begin
                     Memo1.Lines.Clear;
                     Memo1.Lines.Add('start');
                   end);
     TThread.Sleep(2000);
     TThread.Queue(TThread.Current, 
                   procedure
                   begin
                     Memo1.Lines.Add('end');
                   end);
   end
 ).Start;
end;
```

ボタンを押すとメモに `start` が表示され、2 秒後に `end` が表示される...ハズですが、メモにはいつまで経っても `start` だけが表示されていると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/9d3426d4-aa83-3ada-75b8-b3a51ba355fe.png)

ドキュメントには次のように書かれています。

> メイン スレッドは、最終的にキューに入っているすべてのメソッドを処理します。

嘘ではないですが、それはスレッドが終了していない場合の話です。つまり、先にスレッドが終了してしまったので、それ以降のメソッド呼び出しが削除 (キャンセル) されてしまったのです。

#### ・解決方法 1

`Queue()` の最初のパラメータに **nil** を指定します。

```pascal
procedure TForm1.Button1Click(Sender: TObject);
begin
 TThread.CreateAnonymousThread(
   procedure
   begin
     TThread.Queue(TThread.Current,
                   procedure
                   begin
                     Memo1.Lines.Clear;
                     Memo1.Lines.Add('start');
                   end);
     TThread.Sleep(2000);
     TThread.Queue(nil,                      // <- nil を指定 (最後の一つだけで構わない)
                   procedure
                   begin
                     Memo1.Lines.Add('end');
                   end);
   end
 ).Start;
end;
```

`TThread.Queue()` の最初のパラメータが **nil** でない場合、キューに入れられた呼び出しがパラメータで指定されたスレッドと関連付けられ、スレッドが終了すると、まだ処理されていない呼び出しは `TThread.RemoveQueuedEvents()` によりキャンセルされます。


#### ・解決方法 2

ダミーの `Synchronize()` をキューの最後に追加します。

```pascal
procedure TForm1.Button1Click(Sender: TObject);
begin
 TThread.CreateAnonymousThread(
   procedure
   begin
     TThread.Queue(TThread.Current,
                   procedure
                   begin
                     Memo1.Lines.Clear;
                     Memo1.Lines.Add('start');
                   end);
     TThread.Sleep(2000);
     TThread.Queue(TThread.Current,
                   procedure
                   begin
                     Memo1.Lines.Add('end');
                   end);
     TThread.Synchronize(TThread.Current, procedure begin end); // <- ダミーのSynchronize() を追加 
   end
 ).Start;
end;
```

`Synchronize()` がキューの最後に追加されると待ちが発生するため、結果的に `Queue()` で追加された呼び出しはすべて処理される事になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/180f4f60-ecb3-5159-436e-d7a89068574e.png)

:::note warn
Queue() はメッセージループを利用するため、コンソールアプリケーションでは使用できません。
:::
**See also:**

 - [System.Classes.TThread.Queue (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Queue)
 - [Delphi アルゴリズムトレーニング 第2回 単純なキューと循環キュー (@IT)](https://atmarkit.itmedia.co.jp/fcoding/articles/delphi/02/delphi02a.html)
 - [Delphi Queue and Synchronize (StackOverflow)](https://stackoverflow.com/questions/42280937/delphi-queue-and-synchronize)


### 1.1.12. Sleep() クラスメソッド

`TThread.Sleep()` [^11] は指定したミリ秒数だけプログラム実行を停止させます。

現在の実装では `System.Classes.TThread.Sleep()` メソッドと `System.SysUtils.Sleep()` 手続きは同じ処理内容になっているため、単に `Sleep()` と書いても (どちらが使われても) 問題ありません。将来、何かしらの変更が発生する事も考慮して、記事中では `TThread.Sleep()` を使っています。

この **Sleep** を (別のスレッドから) 途中で終了させるメソッドも現在の所では用意されていません。


**See also:**

 - [System.SysUtils.Sleep (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.Sleep)
 - [System.Classes.TThread.Sleep (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.Sleep)

### 1.1.13. SpinWait() クラスメソッド

`TThread.SpinWait()` [^12] は指定したスピンループ分だけプログラム実行を遅延させます。

空の **for** ループを回して時間調整するようなものです。

**See also:**

 - [System.Classes.TThread.SpinWait (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.SpinWait)


### 1.1.14. WaitFor() メソッド

`WaitFor()` メソッドを呼び出すとスレッドが終了するまで待ちます。

`WaitFor()` メソッドが返す値は `ReturnValue` プロパティの値です。`ReturnValue` はスレッドにおける Result 変数のようなものです。

```pascal
procedure TForm1.Button1Click(Sender: TObject);
var
  MyThread: TMyThread;
  rv: Integer;
begin
  MyThread := TMyThread.Create;
  rv := MyThread.WaitFor; // スレッドが終了するのを待つ
  FreeAndNil(MyThread);
  ShowMessage(IntToStr(rv));
end;
```

 - `WaitFor()` メソッドは `FreeOnTerminate` プロパティが True の時には使えません。
 - `WaitFor()` メソッドはスレッドが終了するまで、呼び出し元のスレッドをブロックします。つまりメインスレッドで実行された場合、`WaitFor()` で待つ間はフォームを動かせなくなります。

**See also:**

 - [System.Classes.TThread.WaitFor (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.WaitFor)
 - [System.Classes.TThread.ReturnValue (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TThread.ReturnValue)

### 1.1.15. コンソールアプリケーションでの Synchronize / Queue

先述の通り、コンソールアプリケーションではメッセージループがないため、コンソールアプリケーションでは `Synchronize()` / `Queue()` が動作しません。

次のコンソールアプリケーションを実行しても、何も表示されません (中断は〔Ctrl〕+〔C〕)。

```pascal:ApplePen.dpr
program ApplePen;
{$APPTYPE CONSOLE}
uses
  System.Classes, System.Threading;

type
  TMyThread = class(TThread)
  private
    FData: string;
    procedure MyThreadTerminate(Sender: TObject);
  protected
    procedure Execute; override;
  public
    constructor Create(s: String);
  end;

var
  TermCount: Integer;

constructor TMyThread.Create(s: String);
begin
  inherited Create(True);
  FreeOnTerminate := True;
  OnTerminate := MyThreadTerminate;
  FData := s;
end;

procedure TMyThread.Execute;
begin
  Synchronize(procedure begin Writeln(FData) end);
end;

procedure TMyThread.MyThreadTerminate(Sender: TObject);
begin
  Inc(TermCount);
end;

begin
  TermCount := 0;

  TMyThread.Create('I have a pen.').Start;
  TMyThread.Create('I have an apple.').Start;
  TMyThread.Create('uh...Apple Pen.').Start;

  while TermCount < 3 do
    ;
end.
```

最後のループに `CheckSynchronize()` [^13] を追加すると正しく動作するようになります。

```pascal
  while TermCount < 3 do
    CheckSynchronize;
```

**See also:**

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

## 1.2. TThread のデモ

TThread のデモは、最近の Delphi には含まれていないようです [^14]。古い Delphi だと `$(DELPHI)\DEMOS\Threads` や `Delphi\RTL\Threads`、`Object Pascal\RTL\Threads` 等にあります。

この `Threads` デモは XE ～ XE6 のサンプルリポジトリに残っています。 

 - [XE6 のサンプルリポジトリ (SourceForge)](https://svn.code.sf.net/p/radstudiodemos/code/branches/RadStudio_XE6/)

サンプルリポジトリから `Threads` デモを開くには `[ファイル | バージョン管理リポジトリから開く...]` を選択し、バージョン管理システムとして `Subversion` を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/9d9b8e08-33a4-4c76-6406-f7123fc8f0a7.png)

リポジトリの場所とコピー先には次のように指定します。

| 項目 | 値 |
|:---|:---|
| リポジトリの場所 | [https://svn.code.sf.net/p/radstudiodemos/code/branches/RadStudio_XE6/Object Pascal/RTL/Threads/](https://svn.code.sf.net/p/radstudiodemos/code/branches/RadStudio_XE6/Object%20Pascal/RTL/Threads/) |
| コピー先 | 既存の任意のフォルダ (ここでは C:\Work\Threads) |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/c37e5944-6434-fc29-5790-577f2a0e6787.png)

[OK] ボタンを押すとダウンロードが開始され、ダウンロードが終わるとプロジェクトを選択するダイアログが開くので、`thrddemo.dpr` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/257d15ca-4b51-79c4-9aa9-f0e28b864ff6.png)

ユニット `SortThds.pas` の **uses** に `Types` を追加すると、ヒントやワーニングなしでコンパイルできます。

```pascal:SortThds.pas
unit SortThds;

interface

uses
  Classes, Types, Graphics, ExtCtrls;

...
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/05f00624-1a92-24c2-83c2-3ef3555b1767.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/9f03c949-3834-0d72-2f1a-efbc91e59c98.png)

**See also:**

 - [Synchronize (Delphi) (DocWiki)](https://docwiki.embarcadero.com/CodeExamples/en/Synchronize_(Delphi))
 - [デバッガでのスレッドの凍結および凍結解除 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%83%87%E3%83%90%E3%83%83%E3%82%AC%E3%81%A7%E3%81%AE%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%81%AE%E5%87%8D%E7%B5%90%E3%81%8A%E3%82%88%E3%81%B3%E5%87%8D%E7%B5%90%E8%A7%A3%E9%99%A4)

## 参考

 - [Delphi Tips - マルチスレッドアプリケーション (EDN)](https://support.embarcadero.com/jp/article/35961)
 - [スレッド管理ルーチン (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E7%AE%A1%E7%90%86%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3)
 - [Delphi でのマルチスレッドプログラミング (第11回 エンバカデロ・デベロッパーキャンプ【A2】)](https://edn.embarcadero.com/article/images/39112/a2.pdf)
 - [正しい GUI の作り方 (第16回 エンバカデロ・デベロッパーキャンプ【2C】)](https://edn.embarcadero.com/article/images/40483/2C.pdf)
 - [Delphi でキカイを制御する (第16回 エンバカデロ・デベロッパーキャンプ【2F】)](https://edn.embarcadero.com/article/images/40483/2F.pdf)
 - [TThread の Synchronize と Queue について (山本隆の開発日誌)](https://www.gesource.jp/weblog/?p=538)
 - [C++Builder 2009 のスレッドの基本的な使い方のまとめ (山本隆の開発日誌)](https://www.gesource.jp/weblog/?p=562)
 - [Delphi の TThread.CreateAnonymousThread と無名メソッドを使うと、簡単なスレッド処理なら手軽にかける (山本隆の開発日誌)](https://www.gesource.jp/weblog/?p=5093)
 - [C++ Builder / TThread > スレッドが終了時にソフトを終了する実装 (Qiita: @7of9)](https://qiita.com/7of9/items/0b78b5ef45cf0a09a6cd)
 - [C++ Builder / TTreahd > スレッド終了後の処理 (Qiita: @7of9)](https://qiita.com/7of9/items/705e7fde472ffcc4a7b7)
 - [C++ Builder > TThread:WaitFor() を使うときの注意 > FreeOnTerminate を false にしておく (Qiita: @7of9)](https://qiita.com/7of9/items/1155a8048989c290c64b)
 - [C++ Builder XE4 > TThread > Suspended() 後の Start() > 実行中または一時停止中のスレッドに対しては Start を呼び出せません (Qiita: @7of9)](https://qiita.com/7of9/items/09542ff8bace30712593)
 - [C++ Builder XE4 > TThread > 「実行中または一時停止中のスレッドに対しては Start を呼び出せません。」 > : TThread(/* CreateSuspended= */ true) で作ったスレッドの new 直後の Start は問題ない (Qiita: @7of9)](https://qiita.com/7of9/items/717c13b4202eed14357c)
 - [Delphi で指定フォルダ以下のファイル一覧を取得する (Qiita: @SequencePalladium)](https://qiita.com/SequencePalladium/items/0d2c4744f77b1e280a5b)
 - [Delphi TMessageManager (Qiita: @furudoi)](https://qiita.com/furudoi/items/08f02ca26a4385beb029)
 - [クラスメソッド版 TThread.Synchronize の使いどころ。 (Swanman's Horizon)](https://lyna.hateblo.jp/entry/20100209/1265643004)

# 索引
[ [← 0. はじめに](https://qiita.com/ht_deko/items/c6714835355a83cde088) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/e8c1ff3a4c74e4c2a4f3) ] [ [→ 2. クリティカルセクションとロック](https://qiita.com/ht_deko/items/9f3c7534e2ac406ba7dc) ] 

[^1]: `Resume()` および `Suspend()` メソッドは Delphi 2010 以降で非推奨となっており、使用すると ワーニング W1000 が発生します。.NET においてもこれらのメソッドは非推奨となっています ([Resume()](https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.thread.resume) および [Suspend()](https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.thread.suspend))。
[^2]: パラメータのないコンストラクタは Delphi XE 以降で利用可能です。
[^3]: `Start()` メソッドは Delphi 2010 以降で利用可能です。
[^4]: スタックサイズを指定可能なコンストラクタは Delphi XE 以降で利用可能です。
[^5]: クラスメソッド `NameThreadForDebugging()` は Delphi 2010 以降で利用可能です。
[^6]: ウィザードの [名前付きスレッド] は Delphi 7 以降で利用可能です。
[^7]: 無名メソッドに対応した `Synchronize()` は Delphi 2009 以降で利用可能です。
[^8]: `CreateAnonymousThread()` は Delphi XE 以降で利用可能です。
[^9]: クラスメソッドの `Synchronize()` は Delphi 7 以降で利用可能です。Delphi 2007 以降では `StaticSynchronize()` というクラスメソッドもありますが、単に `Synchronize()` を呼んでいるだけであり、現在では非推奨としてマークされています。
[^10]: `Queue()` メソッドは Delphi 2005 以降で利用可能です。Delphi 2007 以降では `StaticQueue()` というクラスメソッドもありますが、単に `Queue()` を呼んでいるだけであり、現在では非推奨としてマークされています。
[^11]: `TThread.Sleep()` メソッドは Delphi XE 以降で利用可能です。
[^12]: `SpinWait()` メソッドは Delphi 2010 以降で利用可能です。
[^13]: `CheckSynchronize()` メソッドは Delphi 6 以降で利用可能です。
[^14]: `Threads` デモは XE6 まで収録されていました。