CreateAnonymousThread の使い方
Delphi のXE から、CreateAnonymousThread が使用できる様になりました。
スレッドとして、BeginThreadより、使い易く、SynchronizeやTerminate、OnTerminate が使用できます。
優先順位 Priority も指定できるので、通常のスレッドとほとんど同じ動作が可能です。
サンプルプログラムとして、タイマールーチンを作成してみました。
プログラムを作成する場合は、スレッドの実行中に、プログラムを閉じられないようにする必要があります。
その方法は、メカコントロール(測定用)プログラム作成時の注意点に詳しく記されているので、そちらを参照してください。
メインスレッドのプロシージャ、オブジェクトの実行は、実行スレッドプログラム内に、メソッドを定義して行います。
サンプルプログラム TimerTest0
unit Timertest; 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, System.Diagnostics; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; SpinEdit1: TSpinEdit; Label1: TLabel; Button3: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private 宣言 } procedure TimerThreadProc; public { Public 宣言 } flgStop : Boolean; end; var Form1: TForm1; implementation {$R *.dfm} //============================================================================= // スレッド処理用メソッド // TStopWatchの使用には,usesにDiagnosticsが必要です // Stringを使用してもメモリーリークは発生しません //============================================================================= procedure TForm1.TimerThreadProc; var AStopWatch : TStopWatch; FTimeValue : Int64; FTimeDis : Int64; Amin : Integer; Asec : Integer; FSync : TThreadProcedure; FSyncEnd : TThreadProcedure; begin // メソッドの定義 FSync := procedure begin Label1.Caption := Format('残%4d分%2d秒', [Amin, Asec]); Label1.Update; end; FSyncEnd := procedure begin Button1.Enabled := True; Button3.Enabled := True; Winapi.Windows.Beep(700, 1000); end; //--------------- これより下が本体コード ----------------- AStopWatch := TStopwatch.StartNew; FTimeValue := SpinEdit1.Value; FTimeDis := FTimeValue; while not flgStop and (FTimeDis > 0) do begin // FTimeDisの値は秒 FTimeDis := FTimeValue - AStopWatch.ElapsedMilliseconds div 1000; Amin := FTimeDis div 60; Asec := FTimeDis mod 60; TThread.Synchronize(TThread.CurrentThread, FSync); // これがないとCPU使用率が跳ね上がります TThread.Sleep(1); end; // 上の処理が終了したら TThread.Synchronize(TThread.CurrentThread, FSyncEnd); end; //============================================================================= // CreateAnonymousThreadでスレッドを作成して実行します // タイマーの起動や時間経過の監視はスレッドで行います // CreateAnonymousThreadはデフォルトでFreeOnTerminate True で // スレッド終了時自動的に開放されます //============================================================================= procedure TForm1.Button1Click(Sender: TObject); begin Button1.Enabled := False; Button3.Enabled := False; flgStop := False; TThread.CreateAnonymousThread(TimerThreadProc).Start; end; //============================================================================= // タイマーストップ //============================================================================= procedure TForm1.Button2Click(Sender: TObject); begin flgStop := True; end; //============================================================== // フォームのクローズボタンは非表示又はグレイアウトしてください // スレッドの実行中プログラムを終了しないようにします //============================================================== procedure TForm1.Button3Click(Sender: TObject); begin Close; end; end.
次の例は、スレッドの終了を、他のスレッドで待ち合わせて、スレッド終了後の処理をする例です。
OnTerminateを使用すれば、簡単なのですが、一つの例として取り上げました。
タイマーを開始し、終了する前に、再度タイマーをスタートすると、タイマーはリスタートします。
サンプルプログラム TimerTest1
unit TimerTest; 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, System.Diagnostics; type TForm1 = class(TForm) Button1: TButton; SpinEdit1: TSpinEdit; Button2: TButton; Label1: TLabel; Button3: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } flgStop : Boolean; TimerThread : TThread; end; var Form1: TForm1; implementation {$R *.dfm} //===================================================================== // TimerThreadの終了を待ちます // FreeOnTerminate := Trueの場合は // TimerThread = Nil // FreeOnTerminate := Falseの場合は (指定の無い場合はデフォルトでTrue) // TimerThread.Terminate // FreeAndNil(TimerThread) // OnTerminatを使用した方が簡単です。 //===================================================================== procedure WaitThread(TH: pointer); begin WaitForSingleObject(integer(TH^), INFINITE); with Form1 do begin if Assigned(TimerThread) then begin flgStop := True; TimerThread.Terminate; FreeAndNil(TimerThread); end; Button3.Enabled := True; end; // WaitThreadスレッド終了 EndThread(0); end; //================================================================================== // CreateAnonymousThreadでスレッドを作成して実行 // タイマーの起動や時間経過の監視はスレッドで行います // TStopWatchの使用には,usesにDiagnosticsが必要です // Button1.Enabled := False をコメントアウトして次の { } 内を有効にすれば // タイマー動作の途中で Button1をクリックすれ事によりタイマーのリスタートが出来ます。 // (FreeOnTerminate := False の必要があります) //================================================================================== procedure TForm1.Button1Click(Sender: TObject); var AStopWatch : TStopWatch; FTimeValue : Int64; FTimeDis : Int64; Amin : Integer; dmp : Int64; Asec : Integer; msec : Integer; id : Cardinal; begin if Assigned(TimerThread) then begin flgStop := True; TimerThread.Terminate; FreeAndNil(TimerThread); end; flgStop := False; // タイマースレッドの生成 TimerThread := TThread.CreateAnonymousThread( procedure begin AStopWatch := TStopwatch.StartNew; FTimeValue := SpinEdit1.Value; while true do begin // FTimeDisの値は秒 FTimeDis := FTimeValue * 1000 - AStopWatch.ElapsedMilliseconds; dmp := FTimeDis mod 60000; Amin := FTimeDis div 60000; Asec := dmp div 1000; msec :=(dmp mod 1000) div 100 * 100; TThread.Synchronize(TimerThread, procedure begin Label1.Caption := Format('残%4d分%2d秒%3d㍉秒', [Amin, Asec, msec]); Label1.Update; end ); if flgStop then break; if FTimeDis <= 0 then break; // これがないとCPU使用率が跳ね上がります Sleep(10); end; // 無くてもOK AStopWatch.Stop; // 上のループ処理が終了したらビープ音 TThread.Synchronize(TimerThread, procedure begin Winapi.Windows.Beep(500, 1000); end ); end ); if Assigned(TimerThread) then begin Button3.Enabled := False; // スレッド優先順位 TimerThread.Priority := tpNormal; // TimerThreadが終了してもスレッド開放しない指定 // 指定をしない場合は 自動的にスレッド開放 デフォルトは True TimerThread.FreeOnTerminate := False; TimerThread.Start; // TimerThreadの終了を監視するスレッドの生成 CloseHandle(BeginThread(nil, 0, addr(WaitThread), addr(TimerThread.Handle), 0, Id)); end; end; //============================================================================= // タイマーストップ //============================================================================= procedure TForm1.Button2Click(Sender: TObject); begin flgStop := True; end; //============================================================== // フォームのクローズボタンは非表示又はグレイアウトしてください // スレッドの実行中プログラムを終了しないようにします //============================================================== procedure TForm1.Button3Click(Sender: TObject); begin Close; end; procedure TForm1.FormCreate(Sender: TObject); begin Label1.Caption := Format('残%4d分%2d秒%3d㍉秒', [0, 0, 0]); end; end.
メソッドの定義で、実行部分をメインスレッドに置いた場合の例です。
public
{ Public宣言 }
TimerThread : TThread;
の宣言をして
TimerThread := TThread.CreateAnonymousThread();
の関連付けをして、いない為、スレッドの終了に Terminate、OnTerminate 等の使用は出来ません。
サンプル
TimerTest2
unit Timertest; 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, System.Diagnostics; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; SpinEdit1: TSpinEdit; Label1: TLabel; Button3: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private 宣言 } procedure TimerThreadProc; procedure Timedisp(Amin, Asec: integer); procedure Enddisp(Tone, Time: integer); public { Public 宣言 } flgStop : Boolean; end; var Form1: TForm1; implementation {$R *.dfm} //============================= // スレッドの補助ルーチン //============================= procedure TForm1.Timedisp(Amin, Asec: integer); begin Label1.Caption := Format('残%4d分%2d秒', [Amin, Asec]); Label1.Update; end; procedure TForm1.Enddisp(Tone, Time: integer); begin Button1.Enabled := True; Button3.Enabled := True; Winapi.Windows.Beep(Tone, Time); end; //============================================================================= // スレッド処理用メソッド // TStopWatchの使用には,usesにDiagnosticsが必要です // Stringを使用してもメモリーリークは発生しません //============================================================================= procedure TForm1.TimerThreadProc; var AStopWatch : TStopWatch; FTimeValue : Int64; FTimeDis : Int64; Amin : Integer; Asec : Integer; FSync : TThreadProcedure; FSyncEnd : TThreadProcedure; begin // メソッドの定義 FSync := procedure begin // スレッド外のプロシージャ実行 Timedisp(Amin, Asec); end; FSyncEnd := procedure begin // スレッド外のプロシージャ実行 Enddisp(700, 1000); end; //--------------- これより下が本体コード ----------------- AStopWatch := TStopwatch.StartNew; FTimeValue := SpinEdit1.Value; FTimeDis := FTimeValue; while not flgStop and (FTimeDis > 0) do begin // FTimeDisの値は秒 FTimeDis := FTimeValue - AStopWatch.ElapsedMilliseconds div 1000; Amin := FTimeDis div 60; Asec := FTimeDis mod 60; TThread.Synchronize(TThread.CurrentThread, FSync); // これがないとCPU使用率が跳ね上がります TThread.Sleep(1); end; // 上の処理が終了したら TThread.Synchronize(TThread.CurrentThread, FSyncEnd); end; //============================================================================= // CreateAnonymousThreadでスレッドを作成して実行します // タイマーの起動や時間経過の監視はスレッドで行います // CreateAnonymousThreadはデフォルトでFreeOnTerminate True で // スレッド終了時自動的に開放されます //============================================================================= procedure TForm1.Button1Click(Sender: TObject); begin Button1.Enabled := False; Button3.Enabled := False; flgStop := False; TThread.CreateAnonymousThread(TimerThreadProc).Start; end; //============================================================================= // タイマーストップ //============================================================================= procedure TForm1.Button2Click(Sender: TObject); begin flgStop := True; end; //============================================================== // フォームのクローズボタンは非表示又はグレイアウトしてください // スレッドの実行中プログラムを終了しないようにします //============================================================== procedure TForm1.Button3Click(Sender: TObject); begin Close; end; end.
次の例は Terminate CheckTerminated を使用して、スレッドを途中停止させる例です。
サンプル
TimerTest3
unit Timertest; 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, System.Diagnostics; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; SpinEdit1: TSpinEdit; Label1: TLabel; Button3: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private 宣言 } procedure TimerThreadProc; procedure Enddisp(Tone, Time:integer); public { Public 宣言 } TimerThread : TThread; end; var Form1: TForm1; implementation {$R *.dfm} // スレッドの補助ルーチン procedure TForm1.Enddisp(Tone, Time: integer); begin Button1.Enabled := True; Button3.Enabled := True; Winapi.Windows.Beep(Tone, Time); end; //============================================================================= // スレッド処理用メソッド // TStopWatchの使用には,usesにDiagnosticsが必要です // Stringを使用してもメモリーリークは発生しません //============================================================================= procedure TForm1.TimerThreadProc; var AStopWatch : TStopWatch; FTimeValue : Int64; FTimeDis : Int64; Amin : Integer; Asec : Integer; FSync : TThreadProcedure; FSyncEnd : TThreadProcedure; begin // スレッド外のプロシージャ呼び出しメソッドの定義 FSync := procedure begin Label1.Caption := Format('残%4d分%2d秒', [Amin, Asec]); Label1.Update; end; FSyncEnd := procedure begin Enddisp(700, 1000); end; //--------------- これより下が本体コード ----------------- AStopWatch := TStopwatch.StartNew; FTimeValue := SpinEdit1.Value; while not TimerThread.CheckTerminated do begin // FTimeDisの値は秒 FTimeDis := FTimeValue - AStopWatch.ElapsedMilliseconds div 1000; Amin := FTimeDis div 60; Asec := FTimeDis mod 60; TThread.Synchronize(TThread.CurrentThread, FSync); if FTimeDis <= 0 then TimerThread.Terminate; // これがないとCPU使用率が跳ね上がります TThread.Sleep(1); end; // 上の処理が終了したら TThread.Synchronize(TThread.CurrentThread, FSyncEnd); end; //============================================================================= // CreateAnonymousThreadでスレッドを作成して実行します // タイマーの起動や時間経過の監視はスレッドで行います // CreateAnonymousThreadはデフォルトで FreeOnTerminate True です // スレッド終了時自動的に開放されます //============================================================================= procedure TForm1.Button1Click(Sender: TObject); begin Button1.Enabled := False; Button3.Enabled := False; TimerThread := TThread.CreateAnonymousThread(TimerThreadProc); TimerThread.Start; end; //============================================================================= // スレッドの停止 //============================================================================= procedure TForm1.Button2Click(Sender: TObject); begin TimerThread.Terminate; end; //============================================================== // フォームのクローズボタンは非表示又はグレイアウトしてください // スレッドの実行中プログラムを終了しないようにします //============================================================== procedure TForm1.Button3Click(Sender: TObject); begin Close; end; end.
OnTerminate Terminate CheckTerminated を使用したスレッドの例です。
OnTerminateを使用すれば、スレッドの終了を簡単に捉えることができます。
サンプルプログラム TimerTest4
unit Timertest; 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, System.Diagnostics; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; SpinEdit1: TSpinEdit; Label1: TLabel; Button3: TButton; Label2: TLabel; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private 宣言 } procedure TimerThreadProc; procedure Enddisp(Tone, Time:integer); procedure MyThread1Terminate(Sender: TObject); public { Public 宣言 } TimerThread : TThread; end; var Form1: TForm1; implementation {$R *.dfm} //========================== // スレッドの補助ルーチン //========================== procedure TForm1.Enddisp(Tone, Time: integer); begin Winapi.Windows.Beep(Tone, Time); end; //============================================================================= // スレッドの終了割り込みイベント処理 // 指定したスレッドが終了するとイベントが発生しこのプロシージャが呼び出されます //============================================================================= procedure TForm1.MyThread1Terminate(Sender: TObject); begin TimerThread := nil; // Button1.Enabled := True; Button3.Enabled := True; Label2.caption:='破棄しました'; end; //============================================================================= // スレッド処理用メソッド // TStopWatchの使用には,usesにDiagnosticsが必要です // Stringを使用してもメモリーリークは発生しません //============================================================================= procedure TForm1.TimerThreadProc; var AStopWatch : TStopWatch; FTimeValue : Int64; FTimeDis : Int64; Amin : Integer; Asec : Integer; FSync : TThreadProcedure; FSyncEnd : TThreadProcedure; begin // メソッドの定義 FSync := procedure begin Label1.Caption := Format('残%4d分%2d秒', [Amin, Asec]); Label1.Update; end; // スレッド外のプロシージャ呼び出しメソッドの定義 FSyncEnd := procedure begin Enddisp(700, 1000); end; //--------------- これより下が本体コード ----------------- AStopWatch := TStopwatch.StartNew; FTimeValue := SpinEdit1.Value; while not TimerThread.CheckTerminated do begin // FTimeDisの値は秒 FTimeDis := FTimeValue - AStopWatch.ElapsedMilliseconds div 1000; Amin := FTimeDis div 60; Asec := FTimeDis mod 60; TThread.Synchronize(TThread.CurrentThread, FSync); if FTimeDis <= 0 then TimerThread.Terminate; // これがないとCPU使用率が跳ね上がります TThread.Sleep(1); end; // 上の処理が終了したら TThread.Synchronize(TThread.CurrentThread, FSyncEnd); end; //============================================================================= // CreateAnonymousThreadでスレッドを作成して実行します // タイマーの起動や時間経過の監視はスレッドで行います // CreateAnonymousThreadはデフォルトで FreeOnTerminate True です // スレッド終了時自動的に開放されます // スレッド終了時にスレッドを解放したくない場合は // FreeOnTerminate := False; が必要です。 // OnTerminateの指定をします //============================================================================= procedure TForm1.Button1Click(Sender: TObject); begin // スレッドが生成されていたら終了 if TimerThread <> nil then exit; // Button1.Enabled := False; Button3.Enabled := False; TimerThread := TThread.CreateAnonymousThread(TimerThreadProc); TimerThread.OnTerminate := MyThread1Terminate; TimerThread.Start; Label2.caption:='スタートしました。'; end; //============================================================================= // スレッドの停止 //============================================================================= procedure TForm1.Button2Click(Sender: TObject); begin if TimerThread <> nil then TimerThread.Terminate; end; //============================================================== // フォームのクローズボタンは非表示又はグレイアウトしてください // スレッドの実行中プログラムを終了しないようにします //============================================================== procedure TForm1.Button3Click(Sender: TObject); begin Close; end; end.
タイマーサンプル
CreateAnonymousThreadとは関係ないのですが、 TNotifyEvent を使用したタイマーの使用例があったので、検討してみました。
元のSourceのままだと、若干問題があるので修正しました。
タイマー割り込みので、VCL(表示)の変更をした後、タイマー自身を開放しているので、VCL(表示)の条件により、表示が正しく更新される以前に、タイマーが失われ、エラーになる可能性があります。
Synchronize が使用できないので、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, Vcl.ExtCtrls; const TH_MESSAGE = WM_APP + 1; type TForm1 = class(TForm) Button1: TButton; Label1: TLabel; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private 宣言 } procedure WindowsProcd(var Msg: Tmessage); message TH_MESSAGE; // メッセージ WM_APP + 1 処理 procedure PostLabelText(LB: TLabel; Text: string; DispTime: Integer); public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} //========================================================= // SendMessage又はPostMessage送信先 // WParamの値は1しか使用していませんが 参考として Case 文 // 使用しています。 //========================================================= procedure TForm1.WindowsProcd(var Msg: Tmessage); begin case Msg.WParam of 1: begin Label1.Caption := 'タイマー終了'; Button2.Enabled := True; Button1.Enabled := True; end; end; end; //======================================================================== // タイマーを生成してイベントもプロシージャ内で処理をしています。 // タイマーイベント内で、タイマー自身の開放をしているので注意が必要です。 // Vclと同期をとる為 PostMessage を使用しています。 // 普通に TTimerを使用したほうが簡単だし、問題もありません //======================================================================== procedure TForm1.PostLabelText(LB: TLabel; Text: string; DispTime: Integer); type TNotifyEventProc = reference to procedure(Sender: TObject); var Timer: TTimer; {MakeNotifyEventHandler begin} function MakeNotifyEventHandler(const Proc: TNotifyEventProc): TNotifyEvent; var method: TMethod; begin method.Data := PPointer(@Proc)^; method.Code := PPointer(PByte(PPointer(PPointer(@Proc)^)^)+$C)^; Result := TNotifyEvent(method); end; {MakeNotifyEventHandler end} begin LB.Caption := Text; Timer := TTimer.Create(nil); Timer.Enabled := False; Timer.OnTimer := MakeNotifyEventHandler ( procedure (Sender: TObject) begin PostMessage(Form1.Handle, TH_MESSAGE, 1, 0); Timer.Free; end ); Timer.Interval := DispTime * 1000; Timer.Enabled := True; end; //-------------------- Button 類 ------------------------- procedure TForm1.Button1Click(Sender: TObject); begin PostLabelText(Label1, 'タイマースタート', 3); Button1.Enabled := False; Button2.Enabled := False; end; procedure TForm1.Button2Click(Sender: TObject); begin Close; end; end.