サーチ…


バックグラウンド作業にスレッドを使用するレスポンシブな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;

スレッドのハンドルも配列に入れられます。これは、スレッドが完了したことを示すメッセージで受け取ったものであり、スレッドのインスタンス外にあると、アクセスが少し容易になります。スレッドのインスタンス外でハンドルを使用できるようにすることで、結果を得るためにインスタンスを必要としない場合(たとえば、データベースに格納されている場合など)、 FreeOnTerminateTrue設定することもできます。その場合、もちろんインスタンスへの参照を保持する必要はありません。

その楽しみは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;

このメソッドは、最初にメッセージで受信したハンドルを使用してスレッドを検索します。一致するものが見つかった場合は、そのインスタンスを使用してスレッドの結果を取得して報告します( FreeOnTerminateFalse 、覚えていますか?)、インスタンスを解放し、インスタンス参照とハンドルの両方をnilに設定します。より長い関連性。

最後に、スレッドのいずれかがまだ実行中かどうかを確認します。見つからない場合は、「すべて完了」が報告され、 FRunningフラグがFalse設定され、新しい作業バッチを開始することができます。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow