Embarcadero Delphi
GUIの応答性を保ちながらスレッドを実行する
サーチ…
バックグラウンド作業にスレッドを使用するレスポンシブなGUIとスレッドから戻って報告するPostMessage
時間のかかるプロセスの実行中にGUIを応答させるには、GUIがメッセージキューを処理するための非常に精巧な「コールバック」か、(バックグラウンド)(ワーカー)スレッドの使用が必要です。
何らかの作業を行うためにスレッド数をいくつでもオフにするのは問題ではありません。 GUIが中間結果と最終結果を表示したり、進行状況を報告したりするときに、楽しいことが始まります。
GUIに何かを表示するには、コントロールやメッセージキュー/ポンプとのやりとりが必要です。それは常にメインスレッドのコンテキストで行われるべきです。他のスレッドのコンテキストでは決してありません。
これを処理する方法はたくさんあります。
この例では、あなたがそれを設定することで終了した後、GUIは、スレッドのインスタンスにアクセスすることができ、簡単なスレッドを使用してそれを行うことができます方法を示しFreeOnTerminate
するfalse
、そしてスレッドが使用して「完了」したときの報告PostMessage
。
競合条件に関する注意事項:ワーカースレッドへの参照は、フォーム内の配列に保持されます。スレッドが終了すると、配列内の対応する参照はnil-edになります。
これは競合状態の潜在的な原因です。終了する必要のあるスレッドがまだ存在するかどうかを簡単に判断できるようにするために、 "Running"ブール値を使用します。
ロックを使用してこれらのリソースを保護する必要があるかどうかを判断する必要があります。
この例では、それが立っているので、必要はありません。 :彼らは2つのだけの場所で変更されてStartThreads
方法とHandleThreadResults
方法。両方のメソッドは、メインスレッドのコンテキストでのみ実行されます。そのようにしておき、異なるスレッドのコンテキストからこれらのメソッドを呼び出すことを開始しない限り、競合条件を生成する方法はありません。
糸
type
TWorker = class(TThread)
private
FFactor: Double;
FResult: Double;
FReportTo: THandle;
protected
procedure Execute; override;
public
constructor Create(const aFactor: Double; const aReportTo: THandle);
property Factor: Double read FFactor;
property Result: Double read FResult;
end;
コンストラクタはprivateメンバーを設定し、FreeOnTerminateをFalseに設定します。これは、メインスレッドがスレッドインスタンスにその結果を問い合わせることを可能にするため不可欠です。
executeメソッドは、その計算を行い、そのコンストラクタで受け取ったハンドルにメッセージを投稿して、doneを返します。
procedure TWorker.Execute;
const
Max = 100000000;var
i : Integer;
begin
inherited;
FResult := FFactor;
for i := 1 to Max do
FResult := Sqrt(FResult);
PostMessage(FReportTo, UM_WORKERDONE, Self.Handle, 0);
end;
この例では、 PostMessage
の使用が不可欠です。 PostMessage
「ちょうど」メインスレッドのメッセージポンプのキューにメッセージを入れ、それが処理されるのを待つことはありません。本質的に非同期です。あなたがSendMessage
を使用する場合は、自分自身を漬け物にするでしょう。 SendMessage
はメッセージをキューに入れ、処理されるまで待機します。要するに、それは同期的です。
カスタムUM_WORKERDONEメッセージの宣言は、次のように宣言されています。
const
UM_WORKERDONE = WM_APP + 1;
type
TUMWorkerDone = packed record
Msg: Cardinal;
ThreadHandle: Integer;
unused: Integer;
Result: LRESULT;
end;
UM_WORKERDONE
constは、 WM_APP
を値の開始点として使用し、WindowsまたはDelphi VCL(MicroSoftの推奨 )が使用する値に干渉しないようにします。
形
任意のフォームを使用してスレッドを開始できます。以下のメンバーを追加するだけです。
private
FRunning: Boolean;
FThreads: array of record
Instance: TThread;
Handle: THandle;
end;
procedure StartThreads(const aNumber: Integer);
procedure HandleThreadResult(var Message: TUMWorkerDone); message UM_WORKERDONE;
ああ、サンプルコードでは、 Memo1: TMemo;
存在を前提としていMemo1: TMemo;
フォームの宣言では、 "ロギングとレポート"のために使用されます。
FRunning
を使用すると、作業中にGUIがクリックされないようにすることができます。 FThreads
は、作成されたスレッドのインスタンスポインタとハンドルを保持するために使用されます。
スレッドを開始するプロシージャは、かなり簡単な実装です。待機しているスレッドのセットがすでにあるかどうかのチェックから開始します。そうなら、それはただ終了する。そうでない場合は、フラグをtrueに設定し、それぞれにハンドルを提供するスレッドを開始し、「完了」メッセージの送信先を知ります。
procedure TForm1.StartThreads(const aNumber: Integer);
var
i: Integer;
begin
if FRunning then
Exit;
FRunning := True;
Memo1.Lines.Add(Format('Starting %d worker threads', [aNumber]));
SetLength(FThreads, aNumber);
for i := 0 to aNumber - 1 do
begin
FThreads[i].Instance := TWorker.Create(pi * (i+1), Self.Handle);
FThreads[i].Handle := FThreads[i].Instance.Handle;
end;
end;
スレッドのハンドルも配列に入れられます。これは、スレッドが完了したことを示すメッセージで受け取ったものであり、スレッドのインスタンス外にあると、アクセスが少し容易になります。スレッドのインスタンス外でハンドルを使用できるようにすることで、結果を得るためにインスタンスを必要としない場合(たとえば、データベースに格納されている場合など)、 FreeOnTerminate
をTrue
設定することもできます。その場合、もちろんインスタンスへの参照を保持する必要はありません。
その楽しみはHandleThreadResult実装にあります:
procedure TForm1.HandleThreadResult(var Message: TUMWorkerDone);
var
i: Integer;
ThreadIdx: Integer;
Thread: TWorker;
Done: Boolean;
begin
// Find thread in array
ThreadIdx := -1;
for i := Low(FThreads) to High(FThreads) do
if FThreads[i].Handle = Cardinal(Message.ThreadHandle) then
begin
ThreadIdx := i;
Break;
end;
// Report results and free the thread, nilling its pointer and handle
// so we can detect when all threads are done.
if ThreadIdx > -1 then
begin
Thread := TWorker(FThreads[i].Instance);
Memo1.Lines.Add(Format('Thread %d returned %f', [ThreadIdx, Thread.Result]));
FreeAndNil(FThreads[i].Instance);
FThreads[i].Handle := nil;
end;
// See whether all threads have finished.
Done := True;
for i := Low(FThreads) to High(FThreads) do
if Assigned(FThreads[i].Instance) then
begin
Done := False;
Break;
end;
if Done then
begin
Memo1.Lines.Add('Work done');
FRunning := False;
end;
end;
このメソッドは、最初にメッセージで受信したハンドルを使用してスレッドを検索します。一致するものが見つかった場合は、そのインスタンスを使用してスレッドの結果を取得して報告します( FreeOnTerminate
はFalse
、覚えていますか?)、インスタンスを解放し、インスタンス参照とハンドルの両方をnilに設定します。より長い関連性。
最後に、スレッドのいずれかがまだ実行中かどうかを確認します。見つからない場合は、「すべて完了」が報告され、 FRunning
フラグがFalse
設定され、新しい作業バッチを開始することができます。