TThreadクラス
 
スレッド0 TThreadクラスのスレッド
 此処では、スレッドの使用について少し検討をします。
スレッドは、高速に処理が出来るのと、複数のスレッドを並列に処理が出来るので便利な機能です。
特に、通信処理には欠かせない機能です。
本ホームページで紹介をしています、RS232Cコンポーネントも、スレッドを使用しています。
TCP/IP通信も、送受信処理にスレッドが欠かせません。
 左図は、ここでダウンロード出来るサンプルプログラムの ThreadTest0 の実行画面です。
ThreadTest0 は、一般的なスレッドの実行例ですが、インターネットで、スレッドの実行例として検索しても、スレッドを一つだけ起動する例が殆どですので、同じスレッドを二つ起動する例を取り上げてみました。
とにかく、一つ出来れば、簡単だろうと複数同時同じスレッドを起動するのは、例として取り上げてないようです。

 同じスレッドを複数起動するのには、スレッド型を必要な数だけ用意します。
配列にすれば、簡単です。動的配列にすれば、プログラム中で必要な数を確保することも可能です。
 

	Threadary : array[0..1] of TplTimerThread; スレッド型の配列

ここの例ではボタン、パネル等は、固定となっていますが、TButton、TPanel 等の配列を用意し Create を使用してButton、Panel 等も生成すれば、必要な数だけ用意することができます。
メインVCL/CLXスレッドを使用する場合は、 Synchronize を使用しますが、スレッドセーフなら必要無いようですが、何がスレッドセーフなのか判断が難しいようです。
(メモリー関係にも、アクセス保護の機能がありますが、そちらは、Helpを参照して下さい。)
普通のスレッドの使用例は、インターネットで探せば沢山あるのでそれを参考にして下さい。
ここでダウンロード出来るサンプルプログラムでは、スレッドの実行中に、クローズボタンでプログラムを終了するとメモリーリークが発生します。
スレッドの実行中は、プログラムを終了出来ないようにするか、スレッドを終了してからプログラムが終了するようにするする必要があります。

BeginThread

 BeginThreadは、スレッドを簡単に生成、終了することが簡単に出来るのですが、あまり使用例が無いので、少し検討をしてみました。

ラベル異常表示左図は、スレッドの中から、ラベルの表示を変更した場合のフォントサイズの異常表示の例です。
LabelのAutoSizeをTrueに設定しておくと、文字のサイズが正しく設定されない時があります。
発生頻度はかなり高いのですが、プログラム自体の暴走は無いようです。
正しく表示させるためには、AotoSizeをFalseに設定し、更に、文字が表示されるだけの領域を確保しておきます。
StaticTextの場合は、起動時に確保された領域より長い文字を表示しようとすると、AutoSizeが動作せず、領域を超えた部分は表示されません。
プログラムの起動時に、表示する文字の長さ分、表示領域を確保しておけば、正常に表示されます。

 BeginThread のスレッドから、VCL関係を実行する場合は PostMessageを使用するのが一番良いようです。
AotoSizeをTrue のままにしておいても、問題は発生しません。
VCL関係にPostMessageを使用しない場合は、VCLとの同期が完全に取れないので、どのような条件のときハングアップが生じるか分かりません。
しかし、PostMessage を使用した メカコントロールプログラムの場合は、フォームの移動処理、フォームクローズボタンのマウス左ボタンダウンは、メッセージを停止させるので、対策が必要です。

この章で取り上げている プログラム例以外の物も、ダウンロードZip ファイルの中に入っているので、テストしてみて下さい。


ThreadTest1
 次のプログラムリストは、Label 及び StaticText の文字の異常表示が無いようにしたものです。
Label StaticText は、固定長に設定しています。
スレッド内で String 変数が使用されるとメモリーリークが発生するため、String を使用するルーチンは、スレッド外に置いています。
 ここの例では、beginThread が二つ有りますが、二つのスレッドが、同じサブルーチンを同時に実行することは出来ないので、TCriticalSection を使用して衝突を避けています。
Synchronize は beginThread では使用出来ません。
ためしに Button のサイズ、Caption をスレッド内から変更してみましたが、スレッド毎にButtonが違えば、これは特に問題は生じないようです。
同じボタンの場合は TCriticalSection を使用する必要があります。
TChartへのデーターの追加も行ってみましたが、問題なく追加が可能です。
TChartへデーターを追加する場合、二つのスレッドから一つの TChartの同じ Series[*] にデーターを追加する場合は、当然 TCriticalSection を使用してスレッド同士の衝突を避ける必要があります。
又、スレッドが複数の場合はループの中で 必ず Sleep(*) を行い、他のスレッドが実行出来る様にする必要があります。 データーの追加表示は、データーの数が増えると同時に、時間も掛かるようになるので、最大のデーター数に注意する必要があります。
(for ループで連続してデーターの追加のみ行う場合は、表示の更新が行われないので、問題ありません。)
スレッド内から VCL関係を直接使用すると、TChartのようなプログラムの実行には、多くの時間を要するので、スレッドを使用する意味があまりなくなってしまいます。
プログラムを一つしか起動しない場合は良いのですが、画面の表示に沢山の時間を要するインターネット等を同時に起動するとハングアップするようです。
又、OSのバージョン、グラフィクのドライバーによっても、ハングアップする確率が違うようなので注意が必要です。
テストした結果では、XP、VISTAでは、長時間インターネットと同時に実行したらハングアップしました。
WIN7、8、10では、未だ発生の確認は出来ていません。(テストをした範囲内では発生しませんでしたが、本当に発生しないかは、不明です)
TChart、或いは、時間を要する表示は、PostMessage を利用したほうが良いようです。
PostMessageを使用すると、スレッドと、表示のルーチンが切り離されるので、スレッドの処理が早くなります。

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, SyncObjs, VCLTee.TeEngine, VCLTee.Series, Vcl.ExtCtrls,
  VCLTee.TeeProcs, VCLTee.Chart;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Edit3: TEdit;
    Chart1: TChart;
    Series1: TLineSeries;
    Series2: TLineSeries;
    CheckBox1: TCheckBox;
    procedure Button4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
    Procedure WMSysCommand(var Messages: TWMSysCommand);message WM_SysCommand;
  public
    { Public 宣言 }
    procedure ThreadStart1;
    procedure ThreadStart2;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type						// スレッドレコードの定義
  TThreadParams = record			// スレッドに値を渡す
    FButton : TButton;
    FEdit : Tedit;
    FLabel : TLabel;
    FTimeValue : Integer;
    Fidno : Integer;
  end;
  PThreadParams = ^TThreadParams;

var
  StopF : Boolean;				// スレッド停止用フラグ
  CS : TCriticalSection;			// 多重実行防止 スレッドによる実行の衝突を防ぐ

//=============================================================================================
// SYSTEM Command 処理
// 測定用ソフトやメカコントロールソフトの場合 SYSTEM Command メッセージ(Postmessage)処理があると
// 悪影響があるものがあり、特に SC_MOVE は表示の実行をとめ表示の割り込み処理が溜まってしまいます。
// ここの例ではメッセージ(Postmessage)処理を使用していないので、滞ることはありません
//=============================================================================================
Procedure TForm1.WMSysCommand(var Messages: TWMSysCommand); 	// フォームのborder Close ボタンのみ有効の場合
begin
  case (messages.CmdType and $FFF0) of
    SC_SIZE : Messages.Result := 0; 				// サイズ変更キャンセル bsSizeableの時 bsSingleであれば不要
    SC_CLOSE: Button4Click(nil); 				// フォームのborder Closeボタン
//    SC_MOVE : if StopF
//                   then inherited 			// スレッド停止していたらフォーム移動
//                   else Messages.Result := 0; 		// フォームの移動キャンセル
  else
    inherited; 						// 指定以外はデフォルト処理
  end;
end;

//=============================================================================================
// カウント値表示
// スレッド内でStringを扱うとメモリーリークが発生するためサブルーチンにします。
//=============================================================================================
procedure Threadsub(FCont: integer; Param: PThreadParams); 	// スレッド内のカウンターの値表示
begin
  Param^.FEdit.Text := inttostr(FCont);
  Param^.FEdit.Update;
  if not Form1.CheckBox1.Checked then exit;
  case Param^.Fidno of
    1: begin
         if Fcont < 120 then
           Form1.Chart1.Series[0].Add(Fcont)
         else
           Form1.Chart1.Series[0].Clear;
       end;
    2: begin
         if Fcont < 250 then
           Form1.Chart1.Series[1].Add(Fcont)
         else
           Form1.Chart1.Series[1].Clear;
       end;
  end;
//  Form1.Chart1.Update;
end;

//=============================================================================================
// スレッド
// スレッド内で、Labelの文字を変更していますが、VCL(表示ルーチン)と同期が取れないので
// 問題があります。
// Labelを固定サイズにして、表示領域を最大文字数より大きくし、VCLの処理を短くします。
// Button. の変更は特に問題は無いようです。
// このスレッド内で、String を扱う実行文が入るとメモリーリークが発生します。
//=============================================================================================
procedure TimerThread(Param : PThreadParams); 		// スレッドの例
var
  Count: integer;
begin
  while not StopF do begin
    Param^.FLabel.Caption := 'Going'; 			// 'Going' const stringはOK メモリーリークなし。
    Param^.FLabel.Update; 					// LabelをAutoSizeにしておくと、文字のサイズに
                     					// 異常が出る場合があります。
    Param^.FButton.Caption := 'Going'; 			// Button.caption は 特に問題なし
    Param^.FButton.Width := 80; 				// Button with 変更特に問題なし
    Param^.FButton.Height := 30; 				// Button hight 変更特に問題なし

    Count:= 0;
    while Param^.FTimeValue >= Count do begin 		// Countの値待ち
{
     // スレッド内でIntToStr (String)を使用するとメモリーリークが発生します。
   Param^.FEdit.Text := IntToStr(FCont);
    Param^.FEdit.Update;
}
    // 他のスレッドが処理を終了するのを待つ
      CS.Enter;
      try
        // メモリーリークが発生しないようにサブルーチンでStringルーチンを使用します
        Threadsub(Count, Param);
      finally
        CS.Leave; 						// 処理終了
      end;
     // これがないとCPU使用率が跳ね上がります 此処ではタイマー役割も兼用
     // 他のスレッドが実行できるようにするため必ず必要です
      Sleep(5); 						// 用途に応じてスリープ時間を設定する最小値は1
      inc(Count); 						// カウンターインクリメント
    end;
    if not StopF then begin
      Param^.FLabel.Caption := 'Stop'; 			// 'Stop' const stringはOK メモリーリークなし
      Param^.FLabel.Update;
    end;

    Param^.FButton.Caption := 'Stop';
    Param^.FButton.Width := 50;
    Param^.FButton.Height := 25;

    // 終了したらビープ音
    case Param^.Fidno of
      1: Winapi.windows.Beep( 800, 70);
      2: Winapi.windows.Beep(1000, 70);
    end;

    Sleep(200); 						// 用途に応じてスリープ時間を設定する最小値は1
  end;
  case Param^.Fidno of
    1: Param^.FButton.Caption := 'スレッド1';
    2: Param^.FButton.Caption := 'スレッド2';
  end;
  Param^.FButton.Height := 25;
  Param^.FButton.Width := 89;
  Param^.FButton.Enabled := True;
  //スレッド終了
  Dispose(Param); 						// スレッドレコードの破棄 New で確保したメモリーの破棄
  EndThread(0); 						// スレッド終了 無くても自動的に終了 スレッドは破棄されます
end;							// (0) の値は、終了コードで、GetExitCodeThreaで取得出来ます

//=============================================================================================
// 終了処理
//=============================================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  CS.Free; 						// スレッドによる多重実行防止開放
end;

//=============================================================================================
// 初期設定
// Labelを固定サイズにする 固定サイズにしないと表示異常が発生します。
// Labelの表示領域を最大文字数より大きくしておきます。
//=============================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Top := (Screen.Height - Height) div 2;
  Left := (Screen.Width - Width) div 2;
  StopF := True;
  Label1.AutoSize := False; 				// Labelを固定サイズにする 固定サイズにしないと表示異常が発生します。
  Label2.AutoSize := False;
  Label1.Height := 13; 					// Labelの表示領域設定 文字が全て表示出来るだけのサイズを確保します。
  Label1.Width := 65;
  Label2.Height := 13;
  Label2.Width := 65;
  CS := TCriticalSection.Create; 				// スレッドによる多重実行防止生成
end;

//=============================================================================================
// スレッドのスタート処理
//=============================================================================================
procedure TForm1.ThreadStart1;
var
  id : Cardinal;
  Pinfo : PThreadParams;
begin
  Button1.Enabled := False;
  New(Pinfo); 						// 新しい動的変数を作成し,Pinfo をその変数のポインタに設定します。
  Pinfo.FButton := Button1; 				// Button1のポインタ
  Pinfo.FEdit := Edit1; 					// Edit1のポインタ
  Pinfo.FLabel := Label1; 					// Label1のポインタ
  Pinfo.FTimeValue := 120; 					// カウンター値の値
  Pinfo.Fidno := 1;
  CloseHandle(BeginThread(nil, 0, Addr(TimerThread), Pinfo, 0, id)); 	// スレッドの生成
end;

procedure TForm1.ThreadStart2;
var
  id : Cardinal;
  Pinfo : PThreadParams;
begin
  Button2.Enabled := False;
  New(Pinfo); 						// 新しい動的変数を作成し,Pinfo をその変数のポインタに設定します。
  Pinfo.FButton := Button2; 				// Button2のポインタ
  Pinfo.FEdit := Edit2; 					// Edit2のポインタ
  Pinfo.FLabel := Label2; 					// Label2のポインタ
  Pinfo.FTimeValue := 250; 					// カウンター値の値
  Pinfo.Fidno := 2;
  CloseHandle(BeginThread(nil, 0, Addr(TimerThread), Pinfo, 0, id)); 	// スレッドの生成
end;

//=============================================================================================
// スタート、ストップ ボタン関係
//=============================================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  StopF := False;
  ThreadStart1;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  StopF := False;
  ThreadStart2;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  StopF := True;
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  if Button1.Enabled and Button2.Enabled then Close;
end;


end.

ThreadTest3

 次のプログラムリストは、Label 及び StaticText の文字の異常表示が無いようにしたものです。
Label StaticText は、固定長に設定しています。
スレッド内で String 変数が使用されるとメモリーリークが発生するため、String を使用するルーチンは、スレッド外に置いています。
ここの例では、beginThread が二つ有るので、二つのスレッドが同じサブルーチンを同時に実行することは出来ないので、TCriticalSection を使用して衝突を避けています。
Synchronize は beginThread では使用出来ません。
ここまでは、ThreadTest1と同じです。

StaticTextにはハンドルがあるので SendMessage を使用して書き換えを行ってみましたが、固定長で 文字の長さ分領域の確保をしないと、正しく表示されません。
SendMassageを使用しない場合と変わりませんでした。
又、SendMassagは、処理がすむまで戻らないので、実行処理時間の長いものには向いていません。

スレッドの開始時、ハンドルを閉じていますが、
     CloseHandle(BeginThread(nil, 0, Addr(TimerThread), Pinfo, 0, id)); // スレッドの開始
スレッドが終了するわけではありません、単にハンドルがなくなるだけで、スレッドが実行状態に有れば、そのまま維持し続けます。
スレッドは、EndThreadか、スレッドが終了した時点で、自動的に破棄されます。
スレッドが終了した時の状態を知りたい場合は、ハンドルは、スレッドが終了した後で破棄をします。

ダウンロードZipファイルの中の ThreadTest3 は、スレッドの中から 全て SendMessage を使用して表示の変更をしています。
VCLとの同期が取れるのし、スレッドの衝突は発生しませんが、SendMessage は、実行が終了まで、制御が戻らないので、スレッドの実行時間が非常に多く掛かります。
SendMessage の、表示関係は、Update を使用しないと、表示されるのが遅れます。

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, MMSystem, SyncObjs;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    StaticText1: TStaticText;
    StaticText2: TStaticText;
    procedure Button4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
  { Private 宣言 }
    Procedure WMSysCommand(var Messages: TWMSysCommand);message WM_SysCommand;
  public
  { Public 宣言 }
    procedure ThreadStart1;
    procedure ThreadStart2;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type 			// スレッドレコードの定義
  TThreadParams = record 	// スレッドに値を渡す
    FButton : TButton;
    FEdit : Tedit;
    FTimeValue : Integer;
    FHWd : HWND;
  end;
  PThreadParams = ^TThreadParams;

var
  StopF : Boolean; 	// スレッド停止用フラグ
  CS : TCriticalSection; 	// 多重実行防止 実行の衝突を防ぐ

//=============================================================================================
// SYSTEM Command 処理
// 測定用ソフトやメカコントロールソフトの場合 SYSTEM Command でメッセージの処理に
// 悪影響があるものがあり、特に SC_MOVE はメッセージによる表示の実行をとめ表示の
// 処理が溜まってしいます。
//=============================================================================================
Procedure TForm1.WMSysCommand(var Messages: TWMSysCommand); 	// フォームのborder Close ボタンのみ有効の場合
begin 							// メカコントロール用の場合は、minimize Maximize は非表示に設定
  case (messages.CmdType and $FFF0) of
    SC_SIZE : Messages.Result := 0; 				// サイズ変更キャンセル bsSizeableの時 bsSingleであれば不要です
    SC_CLOSE: Button4Click(nil); 				// フォームのborder Closeボタン
    SC_MOVE : if StopF
                  then inherited 				// スレッド停止していたらフォーム移動
                  else Messages.Result := 0; 			// フォームの移動キャンセル
    else
      inherited; 					// 指定以外はデフォルト処理
  end;
end;

//=============================================================================================
// スレッド内のカウンターの値表示
// スレッド内でStringを扱うとメモリーリークが発生するためサブルーチンにします。
//=============================================================================================
procedure Threadsub(FCont: integer; Param: PThreadParams); 	// スレッド内のカウンターの値表示
begin
  Param^.FEdit.Text := inttostr(FCont); 			// 此処でStringが使用されています。
  Param^.FEdit.Update;
end;

//=============================================================================================
// スレッド
// スレッド内のカウンターの値表示は、サブルーチンを呼び出しますが他のスレッドと衝突を避けるため
// TCriticalSectionを使用します。
// Button.Enabled の変更は、フラグの変更のみであとは VCLが自動的に行うので問題ないようです。
// Statictextには、Handleが有るので、SendMessageで表示を変更します
//=============================================================================================
procedure TimerThread(Param : PThreadParams); 		// スレッドの例
var
  Count : integer;
  FPchar1, FPchar2 : pchar;
begin
  FPchar1 := 'Going'; 					// 固定長なのでメモリーリーク問題なし
  FPchar2 := 'Stop'; 					// 固定長なのでメモリーリーク問題なし
  // StopF が true になるまでループする
  while not StopF do begin
    SendMessage(Param^.FHWD, WM_SETTEXT, 0, integer(FPchar1)); 	// Statictextに表示書き換えメッセージを送る
    Count:= 0;
   // カウンターの値が設定値になるまでループする
   while Param^.FTimeValue >= Count do begin 		// Countの値待ち

      CS.Enter; 						// 他のスレッドが処理を終了するのを待つ
      try
        Threadsub(Count, Param);
      finally
        CS.Leave; 						// 処理終了
      end;
     // これがないとCPU使用率が跳ね上がります 此処ではタイマー役割も兼用
     // 又、他のスレッドが正しく動作しません
      Sleep(5); 						// 用途に応じてスリープ時間を設定する最小値は1
      inc(Count); 						// カウンターインクリメント
    end;
    // 終了したらビープ音
    Winapi.windows.Beep(800, 70);
    SendMessage(Param^.FHWD, WM_SETTEXT, 0, integer(FPchar2)); 	// Statictextに表示書き換えメッセージを送る
    Sleep(200); 						// 用途に応じてスリープ時間を設定する最小値は1
    
  end;
  Param^.FButton.Enabled := True;
  //スレッド終了
  Dispose(Param); 						// レコードの破棄
  EndThread(0); 						// スレッド終了
end;

//=============================================================================================
// 終了処理
//=============================================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  CS.Free; 						// スレッド多重実行防止開放
end;

//=============================================================================================
// 初期設定
//=============================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Top := (Screen.Height - Height) div 2;
  Left := (Screen.Width - Width) div 2;
  StopF := True;
  Statictext1.Caption := 'Stop                    '; 		// 表示領域の確保 文字数で確保 又は width で確保する
  Statictext2.Width := 71; 					// 表示領域てwidthで確保
  Statictext1.AutoSize := False; 				// 表示枠サイズ固定
  Statictext2.AutoSize := False;
  CS := TCriticalSection.Create; 				// スレッド多重実行防止生成
end;

//=============================================================================================
// スレッドのスタート
//=============================================================================================
procedure TForm1.ThreadStart1;
var
  id : Cardinal;
  Pinfo : PThreadParams;
begin
  Button1.Enabled := False;
  New(Pinfo);
  Pinfo.FButton := Button1;
  Pinfo.FEdit := Edit1;
  Pinfo.FTimeValue := 120;
  Pinfo.FHWd := Statictext1.Handle;
  CloseHandle(BeginThread(nil, 0, Addr(TimerThread), Pinfo, 0, id)); // スレッドの開始
end;

procedure TForm1.ThreadStart2;
var
  id : Cardinal;
  Pinfo : PThreadParams;
begin
  Button2.Enabled := False;
  New(Pinfo);
  Pinfo.FButton := Button2;
  Pinfo.FEdit := Edit2;
  Pinfo.FTimeValue := 250;
  Pinfo.FHWd := Statictext2.Handle;
  CloseHandle(BeginThread(nil, 0, Addr(TimerThread), Pinfo, 0, id)); // スレッドの開始
end;

//=============================================================================================
// スタートストップ ボタン類
//=============================================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  StopF := False;
  ThreadStart1;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  StopF := False;
  ThreadStart2;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  StopF := True;
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  if Button1.Enabled and Button2.Enabled then Close;
end;


end.

ThreadTest2

 此処ではPostMessageを使用してLabel、StaticText 等の変更を行う場合を取り上げています。
Label、StaticText の AutoSize をTrueに設定していても、 PostMessag を使用すれば、正しく表示されます。
又、TCriticalSection を使用しなくても、ルーチンの多重実行、衝突は発生しません。
但し、実行速度が多少遅くなりますが、スレッドのループには大きな影響は与えません。
 Synchronize は、メッセージを使用しているので、PostMessage を使用すれば、Synchronize と同じ効果を得ることが出来ます。

 注意する点は、メカコントロール実行時は、SC_MOVE コマンドの処理をしないようにします。
 又、フォームのクローズボタンはグレイアウトします。
Windows API の EnableMenuItem を利用して グレイアウトを行いますが EnableMenuItem は、スレッド終了時に、グレイアウトから復帰させることも出来ます。
SC_CLOSEの処理は、フォームのクローズボタンを表示していなければ必要ありません。
特にメカコントロールプログラム、測定プログラムの実行中は、SC_MOVE、フォームのクローズボタンは、マウスでの左ボタンダウン時にメッセージ処理が停滞してしまう為です。
TThreadクラスのスレッドでも、メッセージを使用しているので、同じ処理が必要です、又、スレッドの終了を待たずにプログラムを終了すると、メモリーリークが発生します。
更に、プログラムの実行時間にも注意が必要です。
PostMessaの処理は、ビギンスレッドの Sleep(*) 時間に実行されるので、* 時間の長さに注意が必要です。
前のPostMessage 処理が済まない内に、次のPostMessageを送られて、処理が停滞すると、他のスレッドの実行が困難となります。
しかし、異常終了するような事は発生しません。

 ここでは、スレッドの起動時に、スレッドハンドルを破棄しないようにしています。
スレッドハンドルの破棄は、スレッドが終了してから行い、スレッドの終了の理由が分かるようにしています。
EndThread(FEXITCOAD) の FEXITCOAD の 値を指定すると、スレッド終了後 FEXITCOAD の値を取得することが出来ます。
EndThread 無しで終了すると、 終了コードは 1 の値が帰ってきます。
EndThreadが無くても、スレッドが終了すると、そのスレッドはメモリーから破棄されますが、ハンドルは、明示的に破棄をする必要があります。
スレッドの実行中にハンドルを破棄しても、実行中のスレッドが停止するわけでもなく、スレッドの破棄も起こりません。
スレッドのメモリーからの解放は、あくまでもスレッド終了時点で、自動的になされます。

ダウンロードZipファイルのプログラム例の ThreadTest2 にはTChart を追加した例が入っています。


 PostMessage を使用する為には、メッセージの送り先が必要です。
メッセージの送り先としては、二つあり、
 1: Formのハンドルに PostMessage を送り、ユーザー用メッセージにより、指定の手続き(プロシージャ)を実行する。
 2: 指定の手続きに(プロシージャ)に Windows handle を設定しPostMessage を送る。
です。

メッセージに使用できる値は WM_APP (0x8000) ~ 0xBFFF の範囲です。


ここの例は、ダウンロードZipファイル中のプログラムの一部です//****************** 1:の例 ****************************************************************************


unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

const
  TH_MESSAGE0 = WM_APP; 	// WM_APP through 0xBFFF Messages available for use by applications.
  TH_MESSAGE1 = WM_APP + 1; 	// 0x8000 ~ 0xBFFF アプリケーションで自由に定義できる Messages コード

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
//-----------------------------------------------------------------------------------
// Form1.Handle へのメッセージ処理に組み込まれます。
// Form1.Handle に TH_MESSAGE が message として送られると 指定の実行行(WindowsProcd)
// が呼び出されます
//-----------------------------------------------------------------------------------
    procedure WindowsProcd0(var Msg: Tmessage); message TH_MESSAGE0;
    procedure WindowsProcd1(var Msg: Tmessage); message TH_MESSAGE1;

  public
    { Public 宣言 }
  end;


//=============================================================================================
// Postmessage の処理
//=============================================================================================
// 表示処理
procedure TForm1.WindowsProcd0(var Msg: Tmessage); 		// PostMessage送信先
begin 							
    Threadsub(WParam, PThreadParams(LParam)); 		// 表示ルーチンの呼び出し ポインターに戻す PThreadParams(LParam)
end;

  ・・・・・・・・・
  ・・・・・・・・・

// スレッド終了処理
procedure TForm1.WindowsProcd1(var Msg: Tmessage); 		// PostMessage送信先
begin 							
  Msg do begin
    PThreadParams(LParam).FButton.Enabled := True; 		// スタートボタンイネーブルにする
    CloseHandle(PThreadParams(LParam).ThreadHandle); 		// スレッドの破棄
    Dispose(PThreadParams(LParam)); 				// レコード TThreadParams の破棄
  end;
end;

//=============================================================================================
// スレッドの例 VCL 関係は postmessage を使用しているので
// スレッドから切り離されて表示される為、ラベルが自動サイズでも表示異常は発生しません。
// 但し表示に時間が掛かるので、あまり短い間隔で表示を更新するとメッセージキューが溜まります。
//=============================================================================================
procedure TimerThread(Param : PThreadParams);
begin
  // StopF の ビットが FIdNo ビットと一致するまでまでループする。
  while (StopF and Param^.FIdNo) <> Param^.FIdNo do begin
    PostMessage(Form1.Handle, TH_MESSAGE0, VGOING, cardinal(Param)); 		// 'Going'表示 メッセージ送信後直ぐに制御を戻す
    Param^.Fcount := 0;
   // カウンターの値が設定値になるまでループする。
    while Param^.FTimeValue >= Param^.Fcount do begin 			// Countの値待ち
      PostMessage(Form1.Handle, TH_MESSAGE0, VCOUNTER, cardinal(Param)); 		// カウンターの値表示 メッセージ送信後直ぐに制御を戻す

      // これがないとCPU使用率が跳ね上がります 此処ではタイマー役割も兼用
      Sleep(5); 								// 用途に応じてスリープ時間を設定する最小値は1
                								// VCL の実行時間より短くするとメッセージが停滞します
      inc(Param^.Fcount); 							// カウンターインクリメント
    end;
    // 終了したらビープ音
    Winapi.windows.Beep(Param^.FBeep, 100);
    PostMessage(Form1.Handle, TH_MESSAGE0, VSTOP, cardinal(Param)); 		// 'Stop'表示 メッセージ送信後直ぐに制御を戻す
    Sleep(200); 							// 用途に応じてスリープ時間を設定する最小値は1
  end;

  // スレッドの終了
  PostMessage(Form1.Handle, TH_MESSAGE1, 0, cardinal(Param)); 			// StartBtn イネーブルに メッセージ送信後直ぐに制御を戻す
  EndThread(0); // スレッド終了
end;



//****************** 2:の例 *********************************************************************************

  ・・・・・・・・・
  ・・・・・・・・・


  private
    { Private 宣言 }
    procedure WindowsProcd(var Msg: Tmessage);		// このプロシージャに Windows Handle を割り付ける
  public
    { Public 宣言 }
  end;


var
  FHandle : HWNd; 						// Windows Handle メッセージ用ハンドル


//=============================================================================================
// 初期設定
//=============================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FHandle := AllocateHWnd(WindowsProcd); 			// WindowsProcd Handle の 生成
end;


//=============================================================================================
// 終了処理
//=============================================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  DeallocateHWnd(FHandle); 					// WindowsProcd Handle の破棄
end;


//=============================================================================================
// Postmessage の処理
//=============================================================================================
procedure TForm1.WindowsProcd(var Msg: Tmessage); 		// SendMessage又はPostMessage送信先
begin 							// 直接グラフィク表示ルーチンを呼び出すと正常に動作しない
  with Msg do
    case Msg of 						// 送られた Msg の値によりサブルーチンコール
      WM_APP     : Threadsub(WParam, PThreadParams(LParam)); 	// 表示ルーチンの呼び出し ポインターに戻す PThreadParams(LParam)
      WM_APP + 1 : ThreadendProc(PThreadParams(LParam)); 	// スレッド終了処理
      WM_APP + 2 : Graphsub(WParam, PThreadParams(LParam)); 	// グラフ表示

    else
      Result := DefWindowProc(FHandle, Msg, WParam, LParam); 	// 例外の処理
    end;
end;


//=============================================================================================
// スレッドの例 VCL 関係は postmessage を使用しているので
// スレッドから切り離されて表示される為、ラベルが自動サイズでも表示異常は発生しません。
// 但し表示に時間が掛かるので、あまり短い間隔で表示を更新するとメッセージキューが溜まります。
//=============================================================================================
procedure TimerThread(Param : PThreadParams);
begin
 // Param^.FStop が True になるまで ループする。
  while not Param^.FStop do begin
    PostMessage(FHandle, WM_APP, VGOING, cardinal(Param)); 			// 'Going'表示 メッセージ送信後直ぐに制御を戻す
    Param^.Fcount := 0;
    // カウンターの値が設定値になるまでループする。
    while Param^.FTimeValue >= Param^.Fcount do begin 			// Countの値待ち

      if StopF[Param.FIdNo] then break; 					// 緊急停止フラグだったらブレーク
      PostMessage(FHandle, WM_APP + 2, Param^.Fcount, cardinal(Param)); 		// グラフ表示 メッセージ送信後直ぐに制御を戻す
									// Param^.Fcountの値を WParam値として渡します。
									// PostMessgeの送信先で呼びだされると正しくループ
									// しない事があるので
      // これがないとCPU使用率が跳ね上がります 此処ではタイマー役割も兼用
      // 又、他のスレッドが正しく動作しません
      Sleep(FitevlTime); 							// 用途に応じてスリープ時間を設定する最小値は1
									// VCL の実行時間より短くするとメッセージが停滞します
      inc(Param^.Fcount);							// カウンターインクリメント
    end; 

    if StopF[Param.FIdNo] then Break; // 緊急停止ならブレーク
    // 終了したらビープ音
    Winapi.windows.Beep(Param^.FBeep, 100);

    PostMessage(FHandle, WM_APP, VSTOP, cardinal(Param)); 			// 'Stop'表示 メッセージ送信後直ぐに制御を戻す

    Sleep(FSllepTime); // 用途に応じてスリープ時間を設定する最小値は1
  end;

  PostMessage(FHandle, WM_APP + 1, 0, cardinal(Param)); 			// StartBtn イネーブルに メッセージ送信後直ぐに制御を戻す

  EndThread(FEXITCOAD); 							// 終了コード0を指定しています
end;


  ・・・・・・・・・
  ・・・・・・・・・



ThreadTest2
のプログラムリスト
 ダウンロードZipファイルの内容とは若干ちがいます。
ダウンロードZipファイルには、TChart を追加して、Vclに実行時間が掛かる場合のサンプルとしてあります。
Param^.FCountの値を表示するため、PostMessage を使用していますが、メッセージが停滞すると、その間に、Param^.FCountが更新されてしまいます。
スレッド内で値を変更している場合は、その値そのものを PostMessage のパラメーターにして送ったほうが良いでしょう。

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Label4: TLabel;
    StaticText1: TStaticText;
    Label2: TLabel;
    procedure Button4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure WindowsProcd(var Msg: Tmessage);
    Procedure WMSysCommand(var Messages: TWMSysCommand);message WM_SYSCOMMAND;
  public
    { Public 宣言 }
    procedure ThreadStart1;
    procedure ThreadStart2;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type 				// スレッドレコードの定義
  TThreadParams = record 		// スレッドに値を渡す
    FThreadHandle : integer; 	// スレッドハンドル
    FButton : TButton;
    FEdit : Tedit;
    FLabel : TLabel;
    FTimeValue : Integer; 		// カウンター終了設定値
    FCount : integer; 		// カウンター値
    FIdNo : integer; 		// スレッド判別IDNo
    FBeep : integer; 		// ビープ音程
    FThreadID : Cardinal; 		// スレッドID
    FStop : Boolean; 		// 停止フラグ
    FEXCoad : Cardinal;
  end;
  PThreadParams = ^TThreadParams;

const
  VCOUNTER = 1; 			// カウンターの値表示
  VGOING = 2; 			// 'Going' 表示
  VSTOP = 3; 			// 'Stop' 表示

  FThreadN = 2; 			// スレッドの数
  FTNM = FThreadN - 1; 		// for 用
  FitevlTime = 5; 			// カウンターインターバルタイム
  FSllepTime = 200; 		// タイマーループ停止時間
  FWaitShort =  20; 		// 通常の終了待ち時間
  FWaitTime = 200 * 10; 		// スレッド停止待ち時間

  FEXITCOAD = 0; 			// スレッド終了コード

var
  FHandle : HWNd; 				// Windows Handle スレッド用ハンドル
  StopF : array[0..FTNM] of Boolean; 	// 緊急停止フラグ

  TPPp: array[0..FTNM] of PThreadParams; 	// スレッドレコードの配列

//=============================================================================================
// SYSTEM Command 処理
// 測定用ソフトやメカコントロールソフトの場合 SYSTEM Command で Postmassage に処理に
// 悪影響があるものがあり、特に SC_MOVE は表示の実行をとめ表示の割り込み処理が溜まってしまいます。
// 此処では、スレッド実行時 SC_MOVE コマンドをキャンセルしています。
//=============================================================================================
Procedure TForm1.WMSysCommand(var Messages: TWMSysCommand); 	// フォームのborder Close ボタンのみ有効の場合
var
  II : integer;
  MStopF : Boolean;
begin 							// メカコントロールの場合、minimize Maximize は非表示に設定すること
  MStopF := True;
  case (Messages.CmdType and $FFF0) of
    SC_SIZE : Messages.Result := 0; 				// サイズ変更キャンセル bsSizeableの時 bsSingleであれば不要
//    SC_CLOSE: Button5Click(nil); 				// フォームのborder Closeボタン
    SC_MOVE : begin
                for II := 0 to FTNM do 			// 停止していないスレッドの検索
                   if TPPp[II] <> nil then MStopF := False;
                if MStopF then inherited 			// 全てのスレッド停止していたらフォーム移動
                          else Messages.Result := 0; 		// フォームの移動キャンセル
              end;
    else
      inherited; 						// 指定以外はデフォルト処理
    end;
end;

//=============================================================================================
// VCL 関係はスレッド内で実行しないようにします スレッド内では表示と同期が取れません
// スレッド内でStringを扱うとメモリーリークが発生するので postmessage で此処で扱います
//=============================================================================================
procedure Threadsub(FMode: integer; Param: PThreadParams); 	// スレッド内のカウンターの値表示
var
  SCoadEXE : string;
begin
// Stringの表示
  case FMode of
    VCOUNTER: begin
                Param^.FEdit.Text := inttostr(Param^.FCount); 	// スレッド内でStringを扱うとメモリーリークが発生するため
                Param^.FEdit.Update; 			// サブルーチンにする
              end;
    VGOING:   begin
                Param^.FLabel.Caption := 'Going';
                Param^.FLabel.Update;
              end;
    VSTOP:    begin
                Param^.FLabel.Caption := 'Stop';
                Param^.FLabel.Update;
              end;
    end;
// スレッドの状態取得
  GetExitCodeThread(Param^.FThreadHandle, Param^.FEXCoad); 	// 終了コードの取得

  if Param^.FEXCoad = STILL_ACTIVE then SCoadEXE := 'STILL_ACTIVE'
                                   else SCoadEXE := intTostr(Param^.FEXCoad);
// 終了コード参考表示 STILL_ACTIVE 実行が表示されます。
  if Param^.FIdNo = 0 then
    Form1.StaticText1.Caption := 'ExitNO1 ' + SCoadEXE;
  if Param^.FIdNo = 1 then
    Form1.Label4.Caption := 'ExitNO2 ' + SCoadEXE;
end;

//=============================================================================================
// スレッドの終了処理
// スレッドの終了を待ちスレッドハンドルを解放します
// スレッドの終了コード取得と表示は参考です。
//=============================================================================================
procedure ThreadendProc(Param: PThreadParams);
var
  FEndstr : string;
  delMenu : HMenu;
begin
  WaitForSingleObject(Param^.FThreadHandle, FWaitShort); // スレッドの終了待ち
//  MsgWaitForMultipleObjects(1, &Param^.FThreadHandle, True, FWaitShort, QS_ALLINPUT );
  GetExitCodeThread(Param^.FThreadHandle, Param^.FEXCoad); 	// 終了コードの取得

  CloseHandle(Param^.FThreadHandle); 			// スレッドハンドルの破棄 スレッドはEndThreadで解放されます

  Param^.FButton.Enabled := True; 				// スタートボタンイネーブルにする

  case Param^.FEXCoad of
    FEXITCOAD    : FEndstr := ' 終了';
    STILL_ACTIVE : FEndstr := ' 実行中';
    1            : FEndstr := ' 終了コマンドが無い';
    else FEndstr := ' エラー';

    end;

  if Param^.FIdNo = 0 then 				// 終了コード参考表示
    Form1.StaticText1.Caption := 'ExitNO1 ' + intTostr(Param^.FEXCoad) + FEndstr;
  if Param^.FIdNo = 1 then 				// 終了コード参考表示
    Form1.Label4.Caption := 'ExitNO2 ' + intTostr(Param^.FEXCoad) + FEndstr;

  TPPp[Param^.FIdNo] := nil; 				// 配列ポインタ初期化
  Dispose(Param); // レコード TThreadParams の破棄
  if Form1.Button1.Enabled and Form1.Button2.Enabled then begin
    Form1.Button5.Enabled := True; 				// Close ボタンイネーブル
    Form1.Button6.Enabled := False; 				// 緊急停止ボタンディスエブル
    EnaMenu := GetSystemMenu(Form1.Handle, False);
    EnableMenuItem(
                   EnaMenu, 				// メニューのハンドル
                   SC_CLOSE, 				// 更新対象のメニュー項目
                   MF_ENABLED 				// オプション
                  );
    DrawMenuBar (Form1.Handle);
  end;
end;

//=============================================================================================
// スレッドの例 VCL 関係は postmessage を使用しているので
// スレッドから切り離されて表示される為、ラベルが自動サイズでも表示異常は発生しません。
// 但し表示に時間が掛かるので、あまり短い間隔で表示を更新するとメッセージキューが溜まります。
//=============================================================================================
procedure TimerThread(Param : PThreadParams);
begin
  // Param^.FStop が True になるまで ループする。
  while not Param^.FStop do begin
    PostMessage(FHandle, WM_APP, VGOING, cardinal(Param)); 	// 'Going'表示 メッセージ送信後直ぐに制御を戻す
    Param^.Fcount := 0;
    // カウンターの値が設定値になるまでループする。
    while Param^.FTimeValue >= Param^.Fcount do begin 	// Countの値待ち

      if StopF[Param.FIdNo] then break; 			// 緊急停止フラグだったらブレーク
      PostMessage(FHandle, WM_APP, VCOUNTER, cardinal(Param)); 	// カウンターの値表示 メッセージ送信後直ぐに制御を戻す

     // これがないとCPU使用率が跳ね上がります 此処ではタイマー役割も兼用
     // 又、他のスレッドが正しく動作しません
     Sleep(FitevlTime); 					// 用途に応じてスリープ時間を設定する最小値は1
     							// VCL の実行時間より短くするとメッセージが停滞します
     inc(Param^.Fcount);					// カウンターインクリメント
    end; 

    if StopF[Param.FIdNo] then Break; 			// 緊急停止ならブレーク
    // 終了したらビープ音
    Winapi.windows.Beep(Param^.FBeep, 100);

    PostMessage(FHandle, WM_APP, VSTOP, cardinal(Param)); 	// 'Stop'表示 メッセージ送信後直ぐに制御を戻す

    Sleep(FSllepTime); 					// 用途に応じてスリープ時間を設定する最小値は1
//    Sleep(10000); 					// 緊急停止テスト用
  end;
// Sleep(10000); 						// 緊急停止テスト用

  PostMessage(FHandle, WM_APP + 1, 0, cardinal(Param)); 	// StartBtn イネーブルに メッセージ送信後直ぐに制御を戻す

  EndThread(FEXITCOAD); 					// 終了コード0を指定します
end;

//=============================================================================================
// Postmessage の処理
//=============================================================================================
procedure TForm1.WindowsProcd(var Msg: Tmessage); 	// SendMessage又はPostMessage送信先
begin 			// 直接グラフィク表示ルーチンを呼び出すと正常に動作しない
  with Msg do
    case Msg of 		// 送られた Msg の値によりサブルーチンコール
      WM_APP     : Threadsub(WParam, PThreadParams(LParam)); 	// 表示ルーチンの呼び出し ポインターに戻す PThreadParams(LParam)
      WM_APP + 1 : ThreadendProc(PThreadParams(LParam)); 	// スレッド終了処理

      else
        Result := DefWindowProc(FHandle, Msg, WParam, LParam); 	// 例外の処理
    end;
end;

//=============================================================================================
// 終了処理
//=============================================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  DeallocateHWnd(FHandle); 					// WindowsProcd Handle の破棄
end;

//=============================================================================================
// 初期設定
//=============================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Top := (Screen.Height - Height) div 2;
  Left := (Screen.Width - Width) div 2;

  Button3.Enabled := False;
  Button4.Enabled := False;
  Button6.Enabled := False;

  FHandle := AllocateHWnd(WindowsProcd); 			// WindowsProcd Handle の 生成
end;


//=============================================================================================
// スレッド1の起動
//=============================================================================================
procedure TForm1.ThreadStart1;
var
  delMenu : HMenu;
begin
  Button1.Enabled := False;
  New(TPPp[0]); 				// レコードポインタの生成 レコードの生成
  TPPp[0].FButton := Button1; 		// Button1 のポインター
  TPPp[0].FEdit := Edit1; 			// Edit1のポインター
  TPPp[0].FLabel := Label1; 		// Label1のポインター
  TPPp[0].FTimeValue := 120; 		// カウンターの設定値
  TPPp[0].FIdNo := 0; 			// スレッド番号
  TPPp[0].FBeep := 800; 			// ビープ音音程
  TPPp[0].FStop := False; 			// 通常停止フラグリセット
  TPPp[0].FEXCoad := FEXITCOAD; 		// 終了コード初期化
  StopF[0] := False; 			// 緊急停止フラグリセット
  Statictext1.Caption := 'ExitNO1 ' + intTostr(TPPp[0].FEXCoad);

  TPPp[0].FThreadHandle := BeginThread(nil, 0, Addr(TimerThread), TPPp[0], 0, TPPp[0].FThreadID); 	// スレッドの起動
  if TPPp[0].FThreadHandle = 0 then
    begin
      Dispose(TPPp[0]); 			// レコード TThreadParams の破棄
      application.MessageBox('スレッド1の生成に失敗しました','注意',0);
      Button1.Enabled := True;
      TPPp[0] := nil;
      exit;
    end;
  delMenu := GetSystemMenu(Handle, False);		// メニューハンドルの取得
  EnableMenuItem(delMenu, SC_CLOSE, MF_GRAYED);	// クローズボタンのグレイアウト
  DrawMenuBar (Handle);
  Button3.Enabled := True; 			// 停止ボタンイネーブル
  Button5.Enabled := false; 		// close ボタンディスエブル
  Button6.Enabled := True;
end;

//=============================================================================================
// スレッド2の起動
//=============================================================================================
procedure TForm1.ThreadStart2;
var
  delMenu : HMenu;
begin
  Button2.Enabled := False;
  New(TPPp[1]); 				// レコードポインタの生成
  TPPp[1].FButton := Button2; 		// Button2 のポインター
  TPPp[1].FEdit := Edit2; 			// Edit2のポインター
  TPPp[1].FLabel := Label2; 		// Label2のポインター
  TPPp[1].FTimeValue := 75; 		// カウンターの設定値
  TPPp[1].FIdNo := 1; 			// スレッド番号
  TPPp[1].FBeep := 700; 			// ビープ音音程
  TPPp[1].FStop := False; 			// 通常停止フラグリセット
  TPPp[1].FEXCoad := FEXITCOAD; 		// 終了コード初期化
  Label4.Caption := 'ExitNO2 ' + intTostr(TPPp[1].FEXCoad);

  StopF[1] := False; 			// 緊急停止フラグリセット
  TPPp[1].FThreadHandle := BeginThread(nil, 0, Addr(TimerThread), TPPp[1], 0, TPPp[1].FThreadID); // スレッドの起動
  if TPPp[1].FThreadHandle = 0 then
    begin
      Dispose(TPPp[1]); 			// レコード TThreadParams の破棄
      application.MessageBox('スレッド2の生成に失敗しました','注意',0);
      Button2.Enabled := True;
      TPPp[1] := nil;
      exit;
    end;
  delMenu := GetSystemMenu(Handle, False);    	// メニューハンドルの取得
  EnableMenuItem(delMenu, SC_CLOSE, MF_GRAYED);	// クローズボタンのグレイアウト
  DrawMenuBar (Handle);
  Button4.Enabled := True; 			// 停止ボタンイネーブル
  Button5.Enabled := False; 		// close ボタンディスエブル
  Button6.Enabled := True;
end;

//=============================================================================================
// 起動停止 ボタン関係
//=============================================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  ThreadStart1;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ThreadStart2;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  TPPp[0].FStop := True; 			// 停止フラグ
  Button3.Enabled := False;
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  TPPp[1].FStop := True; 			// 停止フラグ
  Button4.Enabled := False;
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
  if Button1.Enabled and Button2.Enabled then close; // 終了
end;

//=============================================================================================
// スレッド緊急停止
// 最初は停止フラグにより停止を試みて、時間までに停止しなかったら
// 強制的にスレッドを停止します。
//=============================================================================================
procedure TForm1.Button6Click(Sender: TObject); // スレッドの無条件停止 危険
var
  II : integer;
  ExitCoad : DWORD;
  TPPpHWD : HWND;
  FEXit : DWORD;
  StopErr : integer;
begin
  StopErr := 0;
  ExitCoad := 0;
  for II := 0 to FTNM do begin
    if (TPPp[II] <> nil) then begin
      StopF[II] := True; 					// 緊急停止フラグセット
      TPPp[II].FStop := True; 				// スレッドの通常停止フラグをセット
      TPPpHWD := TPPp[II].FThreadHandle;
      FEXit := WaitForSingleObject(TPPpHWD, FWaitTime); 	// スレッドの停止を待つ
      if FEXit <> 0 then begin 				// スレッドが停止しなかったら
        // スレッドを強制停止させると、メモリーリーク データーの異常値等 何が起こるかわかりません
        // 強制停止が発生したらプログラムを終了させましょう。
        if GetExitCodeThread(TPPpHWD, ExitCoad) then 		// スレッドのハンドル 終了コード取得
          if ExitCoad = STILL_ACTIVE then
            Terminatethread(TPPpHWD, 0); 			// スレッドの強制停止
        Closehandle(TPPpHWD); 				// スレッドハンドルの解放
        Dispose(TPPp[II]); 					// レコード TThreadParams の破棄
        TPPp[II] := nil; 					// レコードポインタの初期化
        StopErr := StopErr + II + 1; 			// エラーフラグセット
      end;
      case II of
        0: begin
             Button1.Enabled := True; 			// スタートボタンイネーブル
             Button3.Enabled := False; 			// 停止ボタンデスエブル
           end;
        1: begin
             Button2.Enabled := True; 			// スタートボタンイネーブル
             Button4.Enabled := False; 			// 停止ボタンデスエブル
           end;
      end;
    end;
  end;
  Button5.Enabled := True;
  Button6.Enabled := False;
  if StopErr <> 0 then
     Application.MessageBox('スレッド異常停止しました プログラムを終了して下さい。' ,'注意',0);
end;

end.

前記 プログラムでは、プロシージャにハンドルを割り付けていますが、Form1のハンドルへメッセージを送って処理をする場合は、まず WndProc をオーバライズします。
private
 
****** *******
protected

  procedure WndProc(var Msg: TMessage); override;
public
  ****** *******
end;


ウィンドウプロシージャを新しく書き直します。

procedure TForm1.WndProc(var Msg: TMessage);
begin
  case Msg.Msg of
     WM_APP      : Threadsub(Msg.WParam, PThreadParams(Msg.LParam));  // 表示ルーチンの呼び出し ポインターに戻す PThreadParams(LParam)
     WM_APP + 1 : ThreadendProc(PThreadParams(Msg.LParam));
  else
    inherited
WndProc(Msg);  // 違ったらデフォルト処理
  end;
end;

オーバライズを使用しない場合は
const
    TH_MESSAGE0 = WM_APP ;
    TH_MESSAGE1 = WM_APP + 1;

  private
    { Private 宣言 }
    procedure WindowsProcd0(var Msg: Tmessage); message TH_MESSAGE0; // メッセージ WM_APP      処理
    procedure WindowsProcd1(var Msg: Tmessage); message TH_MESSAGE1; // メッセージ WM_APP + 1 処理
  public
     ****** *******
  end;

procedure TForm1.WindowsProcd0(var Msg: Tmessage);
begin
  ---
  ---
end;

procedure TForm1.WindowsProcd1(var Msg: Tmessage);
begin
  ---
  ---
end;

この様にすることで、Form1のハンドルにメッセージを送ることで処理が出来ますが、プログラムミスの防止、動作の安定性を考慮すると、新しくプロシージャにハンドルを割り付けて処理をしたほうが良いと思います。

ThreadTest6

 
同じオブジェクトにアクセスする場合は、MonitorEnter MonitorExit を使用して衝突をさけます。
MonitorEnterは、オブジェクトを、たのスレッドが使用している場合、開放されるのを待ちますが、設定された待ち時間を過ぎても開放されない場合、False を返すので、開放されるまで、長い時間待つことを避けることが出来まし、オブジェクトを実行したかどうかの確認にもなります。
次はその使用例です。

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.SyncObjs;

type                                              //  スレッドレコードの定義
  TThreadParams = record                          //  スレッドに値を渡す
    FEdit      : Tedit;
    FLabel     : TLabel;
    FTimeValue : Integer;
    Fidno      : Integer;
    FStopF     : boolean;
    FButton    : TButton;
    FStopBtn   : TButton;
  end;
  PThreadParams = ^TThreadParams;

type
  TForm1 = class(TForm)
    StartBtn1: TButton;
    StopBtn1: TButton;
    StartBtn2: TButton;
    StopBtn2: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Edit3: TEdit;
    procedure StartBtn1Click(Sender: TObject);
    procedure StopBtn1Click(Sender: TObject);
    procedure StartBtn2Click(Sender: TObject);
    procedure StopBtn2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
    Procedure WMSysCommand(var Messages: TWMSysCommand);message WM_SYSCOMMAND;
    procedure EditOut(ID, NO: integer);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;
implementation

{$R *.dfm}
var
  Pinfo1    : PThreadParams;
  Pinfo2    : PThreadParams;
  CS        : TCriticalSection;                    // 多重実行防止 実行の衝突を防ぐ
  EDCount   : integer;
  Edit3Str  : String;

//=============================================================================================
// SYSTEM Command 処理
//  測定用ソフトやメカコントロールソフトの場合 SYSTEM Command メッセージ(Postmessage)処理があると
//  悪影響があるものがあり、特に SC_MOVE は表示の実行をとめ表示の割り込み処理が溜まってしまいます。
//  ここの例ではメッセージ(Postmessage)処理を使用していないので、滞ることはありません
//=============================================================================================
Procedure TForm1.WMSysCommand(var Messages: TWMSysCommand);         // フォームのborder Close ボタンのみ有効の場合
begin                                                               // メカコントロール用の場合は、minimize Maximize は非表示に設定すること
  case (messages.CmdType and $FFF0) of
    SC_SIZE : Messages.Result := 0;                                 // サイズ変更キャンセル bsSizeableの時 bsSingleであれば不要
    SC_CLOSE: if StartBtn1.Enabled and StartBtn2.Enabled
                    then inherited
                    else Messages.Result := 0;

//    SC_MOVE : if StartBtn1.Enabled and StartBtn2.Enabled
//                    then inherited                                  // スレッド停止していたらフォーム移動
//                    else Messages.Result := 0;                      // フォームの移動キャンセル
  else
    inherited;                                                      // 指定以外はデフォルト処理
  end;
end;

//==================================================================================================
// スレッド
// MonitorEnter   MonitorExit の使用例です。
// MonitorEnter   MonitorExit が 無いと Edit3 の 文字表示に たまに二つ繋がった値が表示されます
// ここでの Edit3 の表示は、 0,1,2,3...99,100 とならず 2,4,5,.98,100 の様に値が跳ぶことがあります。
// 値を連続での表示は、 EditOut の Edit3 表示となります。
//==================================================================================================
procedure ThreadProc(Param : PThreadParams);        // スレッドの例;
var
  FCount : integer;
begin
  FCount := 0;
  Param.FStopBtn.Enabled := True;
  while not Param.FStopF do begin
    Param.FLabel.Caption := 'Going';
    Param.FLabel.Update;
    while Param.FTimeValue >= Fcount do begin
      Form1.EditOut(Param.Fidno, Fcount);

      if MonitorEnter(Form1.Edit3, 500) then        // Edit3 オブジェクト多重アクセス禁止設定 500 は Wait time
        try
          Form1.Edit3.SelectAll;
          Form1.Edit3.SetSelText(Edit3Str);         // Form1.Edit3.text := Edit3Str は NG
//          Form1.Edit3.SetSelTextBuf(Pchar(Edit3Str));
          Form1.Edit3.Update;
        finally
          MonitorExit(Form1.Edit3);                 // Edit3 オブジェクト多重アクセス禁止解除
        end;
//      SendMessage(Form1.Edit3.Handle, WM_SETTEXT, 0, integer(Pchar(Edit3Str)));  // Edit3に表示書き換えメッセージを送る
      Sleep(10);                                    // Sleep(*) 他のスレッドを動作させるため必ず必要です
      Inc(Fcount);
    end;
    Param.FLabel.Caption := 'Stop';
    Param.FLabel.Update;
  // 終了したらビープ音
    case Param^.Fidno of
      1: Winapi.windows.Beep( 800, 70);
      2: Winapi.windows.Beep(1000, 70);
    end;
    sleep(100);
    FCount := 0;
  end;
  Param.FButton.Enabled := True;
  Param.FStopBtn.Enabled := False;
  Dispose(Param);                                   // スレッドレコードの破棄 New で確保したメモリーの破棄
  EndThread(0);                                     // スレッド終了 無くても自動的に終了 スレッドは破棄されます
end;

//=============================================================================================
// スレッド内で String  を変更、String を扱うサブルーチンを使用出来ないのでここで実行します。
//=============================================================================================
procedure TForm1.EditOut(ID, NO: integer);
begin
  case ID of
    1:  begin
          Edit1.Text := intTostr(NO);
          Edit1.Update;
        end;
    2:  begin
          Edit2.Text := intTostr(NO);
          Edit2.Update;
        end;
  end;
  CS.Enter;                                       // 多重アクセス禁止     Edit3の表示をしなければ無くてもOK
  try
    if EDCount > 100 then begin
      if EDCount > 150 then EDCount := -1
    end
    else begin
      Edit3Str := inTtostr(EDCount);
//      Edit3.SelectAll;
//      Edit3.SetSelText(Edit3Str);
//      Edit3.Update;
    end;
    inc(EDCount);
  finally
    CS.Leave;                                     // 多重アクセス禁止処理終了
  end;
end;

//=============================================================================================
// スレッドのスタート。
//=============================================================================================
procedure TForm1.StartBtn1Click(Sender: TObject);
var
  id        : Cardinal;
begin
  StartBtn1.Enabled := False;
  New(Pinfo1);                                // 新しい動的変数を作成し,Pinfo をその変数のポインタに設定します。
  Pinfo1.FEdit       := Edit1;
  Pinfo1.FLabel      := Label1;
  Pinfo1.FTimeValue  := 100;
  pinfo1.Fidno       := 1;
  pinfo1.FStopF      := False;
  pinfo1.FButton     := StartBtn1;
  pinfo1.FStopBtn    := StopBtn1;
  CloseHandle(BeginThread(nil, 0, @ThreadProc, Pinfo1, 0, id));  // スレッドの生成
end;

procedure TForm1.StartBtn2Click(Sender: TObject);
var
  id        : Cardinal;
begin
  StartBtn2.Enabled := False;
  New(Pinfo2);                                // 新しい動的変数を作成し,Pinfo をその変数のポインタに設定します。
  Pinfo2.FEdit       := Edit2;
  Pinfo2.FLabel      := Label2;
  Pinfo2.FTimeValue  := 110;
  pinfo2.Fidno       := 2;
  pinfo2.FStopF      := False;
  pinfo2.FButton     := StartBtn2;
  pinfo2.FStopBtn    := StopBtn2;
  CloseHandle(BeginThread(nil, 0, @ThreadProc, Pinfo2, 0, id));  // スレッドの生成
end;

//=============================================================================================
// スレッドの停止
//=============================================================================================
procedure TForm1.StopBtn1Click(Sender: TObject);
begin
  Pinfo1.FStopF := True;
  Pinfo1 := nil;                              // Pinfo1の初期化 メモリーの解放はスレッドで行われます。
end;

procedure TForm1.StopBtn2Click(Sender: TObject);
begin
  Pinfo2.FStopF := True;
  Pinfo2 := nil;                              // Pinfo2の初期化 メモリーの解放はスレッドで行われます。
end;

//=============================================================================================
// 初期設定
//=============================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Top := 200;
  Left := (Screen.Width - Width) div 2;
  Label1.AutoSize := False;
  Label1.Width := 100;
  Label1.Caption := 'Stop';
  Label2.AutoSize := False;
  Label2.Width := 100;
  Label2.Caption := 'Stop';
  Edit1.Text := '0';
  Edit2.Text := '0';
  Edit3.Text := '0';
  CS     := TCriticalSection.Create;          // スレッド多重実行防止生成
end;

//=============================================================================================
// 終了処理
//=============================================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  CS.Free;                                    // スレッド多重実行防止解放
end;

end.

スレッドの終了の待ち合わせ

 何らかの理由により、スレッドの終了を待ち合わせる必要がある場合、
While True do application.ProcessMessagesを使用して、待ち合わせるのは、あまり良い方法ではありません。
WaitForsingleobject(Handle, INFINITE); を使用して、スレッドで他のスレッドの終了を待ち合わせる方法が良いのでプログラムを作成してみました。
これは、ダウンロードZIPファイルの中には入っていません。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Samples.Spin, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Panel1: TPanel;
    SpinEdit1: TSpinEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  flgStop : Boolean;
  Thread1 : Integer;

//-------------------------------------------------------
// スレッドレコードの定義
// スレッドに値を渡す
//-------------------------------------------------------
type
  TThreadParams = record
    FButton    : TButton;
    FPanel     : TPanel;
    FTimeValue : Cardinal;
    FEndCoad   : Cardinal;
  end;
  PThreadParams = ^TThreadParams;

//--------------------------------------------------------
// タイマーの値表示
// UnicodeStringを使用するのでメインスレッドで表示します
// スレッド内で使用するとメモリーリークが発生します
//--------------------------------------------------------
procedure PanelCaption(FTimeDis: Cardinal; Fpanel : Tpanel);
var
  Amin, Asec : Integer;
begin
  // 分と秒の値
  Amin := Trunc(FTimeDis / 60);
  Asec := Trunc(FTimeDis) - Amin * 60;
  // 表示更新
  FPanel.Caption := Format('残%4d分%2d秒', [Amin, Asec]);
  FPanel.Update;
end;

//--------------------------------------------------------
//  スレッドの実行部分
//--------------------------------------------------------
procedure TimerThread(Param : PThreadParams);
var
  FTimeStart : Cardinal;
  FTimeDis   : Cardinal;
begin
  Param^.FButton.Enabled := False;

  FTimeStart := GetTickCount;
  while True do begin
    // 残り時間を秒に換算
    FTimeDis := Param^.FTimeValue - (GetTickCount - FTimeStart) div 1000;
    // 表示更新
    PanelCaption(FTimeDis, Param^.FPanel);

    if flgStop then begin
      Param^.FEndCoad := 10;
      break;
    end;
    if FTimeDis <= 0 then break;

    // これがないとCPU使用率が跳ね上がります
    Sleep(10);
  end;
  Param^.FButton.Enabled := True;
  // メモリーの開放
  Dispose(Param);
  // 終了したらビープ音
  Winapi.windows.Beep(800, 1200);
  // スレッド終了
  EndThread(Param^.FEndCoad);
end;

//--------------------------------------------------------
// スレッド1の終了コード表示
//--------------------------------------------------------
procedure endcodedisp(FEXCoad : Cardinal);
begin
  Form1.Panel1.Caption := '終了コード = ' + IntTostr(FEXCoad);
end;

//-------------------------------------------------------------------------------------------
// スレッド1の終了待ちスレッドです。
// 表示関係がメインスレッドにあるので、メインスレッドでは、スレッド1の待ち合わせは出来ません。
// メインスレッドで待ち合わせをすると表示が停止しします。(メインスレッドが停止します)。
// スレッド1が終了するまで、このスレッドが停止します。
//-------------------------------------------------------------------------------------------
procedure WaitThread(TH: pointer);
var
  FEXCoad : Cardinal;
begin
  WaitForSingleObject(integer(TH^), INFINITE); // スレッド1の終了待ち
  GetExitCodeThread(integer(TH^), FEXCoad);    // スレッド1の終了コードの取得
  endcodedisp(FEXCoad);                        // 終了コードの表示
  CloseHandle(integer(TH^));                   // スレッド1のハンドルの解放
  EndThread(0);                                // スレッド終了
end;

//=============================================================================
//  スレッドを作成して実行
//  タイマーの起動や時間経過の監視はスレッドで行います
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  Thread2 : Integer;
  pInfo   : PThreadParams;
  id1,id2 : Cardinal;
begin
  flgStop := False;
  // メモリを確保し,スレッドに渡す値をセットしてスレッド開始
  New(pInfo);
  pInfo^.FButton    := Button1;
  pInfo^.FPanel     := Panel1;
  pInfo^.FTimeValue := SpinEdit1.Value;
  pInfo^.FEndCoad   := 11;
  // スレッド1の開始
  Thread1 := BeginThread(nil, 0, Addr(TimerThread), pInfo, 0, Id1);
  // スレッド1の終了を待つスレッド2の起動
  if Thread1 <> 0 then begin
    Thread2 := BeginThread(nil, 0, addr(WaitThread), addr(Thread1), 0, Id2);
    // スレッド2ハンドルクローズ クローズしてもスレッドは終了しません
    CloseHandle(Thread2);
  end;
end;

//=============================================================================
//  タイマーストップ
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  flgStop := True;
end;

end.



まとめ


TThreadクラスのスレッド
    1.表示(VCL)を使用する場合はメインスレッドと同期を取るため、Synchronize を使用します。
    2.表示の必要が無い場合、共通のサブルーチンコール、メモリーアクセスは、TCriticalSection を使用して衝突をさけます。
  3.メカコントロールの場合フォームの移動開始時、Synchronize 処理の一瞬の停滞を防ぐため、スレッド実行時、移動処理をしないようにします。
  4.スレッドを停止しないで、プログラムを終了すると、メモリーリークが発生するので、メニューのクローズボタンは、グレーアウトするか、表示しないようにします。
  5.同じオブジェクトにアクセスする場合は、MonitorEnter MonitorExit を使用して衝突をさけます。

BeginThread
    1.表示(VCL)を使用する場合はメインスレッドと同期を取るため、PostMessage Sendmessageを使用します。
    2.表示の必要が無い場合、共通のサブルーチンコール、メモリーアクセスは、TCriticalSection を使用して衝突をさけます。
  3.メカコントロールの場合フォームの移動開始時、、PostMessage 処理の一瞬の停滞を防ぐため、スレッド実行時、移動処理をしないようにします。
  4.スレッドを停止しないで、プログラムを終了すると、メモリーリークが発生するので、メニューのクローズボタンは、グレーアウトするか、表示しないようにします。
  5.同じオブジェクトにアクセスする場合は、MonitorEnter MonitorExit を使用して衝突をさけます。

download Threadsample.zip



      最初に戻る