23

私はいつも、いくつかの手順、特に完了するまでに時間がかかる手順を作成するためのより良い方法がないか疑問に思っていました。

私は常にメイン GUI スレッドからすべてを実行してきましたが、これはアプリケーションが応答しなくなり、ここでは実際には役に立たないため、悪いことだと理解して認識してApplication.ProcessMessagesいます。

これにより、たとえばファイルのコピーなどの時間のかかる操作には TThreads を使用する必要があると思います。これはまた、操作を一時停止、再開、または停止できるようにするなど、一部のアプリケーションがどのように完全な制御を提供するのか疑問に思います。

私が取り組んでいる個人的なプロジェクトでは、TProgressBar をオンにしてダイアログ フォームを表示する、約 3 つの長い操作があります。これは機能しますが、もっとうまくできると思います。これらの進行状況ダイアログが長時間表示される可能性があるため、操作をキャンセルして、後でジョブを終了することをお勧めします。

私が言ったように、現在私は Main Gui Thread を実行していますが、代わりに TThreads を使用する必要がありますか? 以前にそれらを使用したことがないため、それらの実装をどこでどのように開始すればよいかわかりません。スレッドが必要な場合、操作の一時停止、再開、停止など、スレッドは必要なものを提供しますか?

基本的に、時間のかかる操作を処理および管理するためのより良い方法を探しています。

4

4 に答える 4

17

はい、これは間違いなく、タスクを実行するためにスレッドが必要な場合です。

スレッドを一時停止/再開し、スレッドをキャンセルする方法の小さな例。

進行状況は、PostMessage 呼び出しを介してメイン スレッドに送信されます。一時停止/再開とキャンセルはTSimpleEvent信号で行います。

編集: @mghie からのコメントによると、より完全な例を次に示します。

編集 2:スレッドが重い作業を呼び出すための手順を渡す方法を示します。

編集 3:いくつかの機能とテスト ユニットを追加しました。

unit WorkerThread;

interface

uses Windows, Classes, SyncObjs;

type
  TWorkFunction = function: boolean of object;

  TWorkerThread = Class(TThread)
  private
    FCancelFlag: TSimpleEvent;
    FDoWorkFlag: TSimpleEvent;
    FOwnerFormHandle: HWND;
    FWorkFunc: TWorkFunction; // Function method to call
    FCallbackMsg: integer; // PostMessage id
    FProgress: integer;
    procedure SetPaused(doPause: boolean);
    function GetPaused: boolean;
    procedure Execute; override;
  public
    Constructor Create(WindowHandle: HWND; callbackMsg: integer;
      myWorkFunc: TWorkFunction);
    Destructor Destroy; override;
    function StartNewWork(newWorkFunc: TWorkFunction): boolean;
    property Paused: boolean read GetPaused write SetPaused;
  end;

implementation

constructor TWorkerThread.Create(WindowHandle: HWND; callbackMsg: integer;
  myWorkFunc: TWorkFunction);
begin
  inherited Create(false);
  FOwnerFormHandle := WindowHandle;
  FDoWorkFlag := TSimpleEvent.Create;
  FCancelFlag := TSimpleEvent.Create;
  FWorkFunc := myWorkFunc;
  FCallbackMsg := callbackMsg;
  Self.FreeOnTerminate := false; // Main thread controls for thread destruction
  if Assigned(FWorkFunc) then
    FDoWorkFlag.SetEvent; // Activate work at start
end;

destructor TWorkerThread.Destroy; // Call MyWorkerThread.Free to cancel the thread
begin
  FDoWorkFlag.ResetEvent; // Stop ongoing work
  FCancelFlag.SetEvent; // Set cancel flag
  Waitfor; // Synchronize
  FCancelFlag.Free;
  FDoWorkFlag.Free;
  inherited;
end;

procedure TWorkerThread.SetPaused(doPause: boolean);
begin
  if doPause then
    FDoWorkFlag.ResetEvent
  else
    FDoWorkFlag.SetEvent;
end;

function TWorkerThread.StartNewWork(newWorkFunc: TWorkFunction): boolean;
begin
  Result := Self.Paused; // Must be paused !
  if Result then
  begin
    FWorkFunc := newWorkFunc;
    FProgress := 0; // Reset progress counter
    if Assigned(FWorkFunc) then
      FDoWorkFlag.SetEvent; // Start work
  end;
end;

procedure TWorkerThread.Execute;
{- PostMessage LParam:
  0 : Work in progress, progress counter in WParam
  1 : Work is ready
  2 : Thread is closing
}
var
  readyFlag: boolean;
  waitList: array [0 .. 1] of THandle;
begin
  FProgress := 0;
  waitList[0] := FDoWorkFlag.Handle;
  waitList[1] := FCancelFlag.Handle;
  while not Terminated do
  begin
    if (WaitForMultipleObjects(2, @waitList[0], false, INFINITE) <>
      WAIT_OBJECT_0) then
      break; // Terminate thread when FCancelFlag is signaled
    // Do some work
    readyFlag := FWorkFunc;
    if readyFlag then // work is done, pause thread
      Self.Paused := true;
    Inc(FProgress);
    // Inform main thread about progress
    PostMessage(FOwnerFormHandle, FCallbackMsg, WPARAM(FProgress),
      LPARAM(readyFlag));
  end;
  PostMessage(FOwnerFormHandle, FCallbackMsg, 0, LPARAM(2)); // Closing thread
end;

function TWorkerThread.GetPaused: boolean;
begin
  Result := (FDoWorkFlag.Waitfor(0) <> wrSignaled);
end;

end.

スレッド操作MyThread.Paused := trueを一時停止して再開するために呼び出すだけです。MyThread.Paused := false

スレッドをキャンセルするには、 を呼び出しますMyThread.Free

投稿されたメッセージをスレッドから受け取るには、次の例を参照してください。

unit Unit1;

interface

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

const
  WM_MyProgress = WM_USER + 0; // The unique message id

type
  TForm1 = class(TForm)
    Label1: TLabel;
    btnStartTask: TButton;
    btnPauseResume: TButton;
    btnCancelTask: TButton;
    Label2: TLabel;
    procedure btnStartTaskClick(Sender: TObject);
    procedure btnPauseResumeClick(Sender: TObject);
    procedure btnCancelTaskClick(Sender: TObject);
  private
    { Private declarations }
    MyThread: TWorkerThread;
    workLoopIx: integer;

    function HeavyWork: boolean;
    procedure OnMyProgressMsg(var Msg: TMessage); message WM_MyProgress;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }
const
  cWorkLoopMax = 500;

function TForm1.HeavyWork: boolean; // True when ready
var
  i, j: integer;
begin
  j := 0;
  for i := 0 to 10000000 do
    Inc(j);
  Inc(workLoopIx);
  Result := (workLoopIx >= cWorkLoopMax);
end;

procedure TForm1.btnStartTaskClick(Sender: TObject);
begin
  if not Assigned(MyThread) then
  begin
    workLoopIx := 0;
    btnStartTask.Enabled := false;
    btnPauseResume.Enabled := true;
    btnCancelTask.Enabled := true;
    MyThread := TWorkerThread.Create(Self.Handle, WM_MyProgress, HeavyWork);
  end;
end;

procedure TForm1.btnPauseResumeClick(Sender: TObject);
begin
  if Assigned(MyThread) then
    MyThread.Paused := not MyThread.Paused;
end;

procedure TForm1.btnCancelTaskClick(Sender: TObject);
begin
  if Assigned(MyThread) then
  begin
    FreeAndNil(MyThread);
    btnStartTask.Enabled := true;
    btnPauseResume.Enabled := false;
    btnCancelTask.Enabled := false;
  end;
end;

procedure TForm1.OnMyProgressMsg(var Msg: TMessage);
begin
  Msg.Msg := 1;
  case Msg.LParam of
    0:
      Label1.Caption := Format('%5.1f %%', [100.0 * Msg.WParam / cWorkLoopMax]);
    1:
      begin
        Label1.Caption := 'Task done';
        btnCancelTaskClick(Self);
      end;
    2:
      Label1.Caption := 'Task terminated';
  end;
end;

end.

そしてフォーム:

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 163
  ClientWidth = 328
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -13
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 120
  TextHeight = 16
  object Label1: TLabel
    Left = 79
    Top = 18
    Width = 51
    Height = 16
    Caption = 'Task idle'
  end
  object Label2: TLabel
    Left = 32
    Top = 18
    Width = 41
    Height = 16
    Caption = 'Status:'
  end
  object btnStartTask: TButton
    Left = 32
    Top = 40
    Width = 137
    Height = 25
    Caption = 'Start'
    TabOrder = 0
    OnClick = btnStartTaskClick
  end
  object btnPauseResume: TButton
    Left = 32
    Top = 71
    Width = 137
    Height = 25
    Caption = 'Pause/Resume'
    Enabled = False
    TabOrder = 1
    OnClick = btnPauseResumeClick
  end
  object btnCancelTask: TButton
    Left = 32
    Top = 102
    Width = 137
    Height = 25
    Caption = 'Cancel'
    Enabled = False
    TabOrder = 2
    OnClick = btnCancelTaskClick
  end
end
于 2012-06-30T22:55:53.883 に答える
6

次のように、スレッド化に高レベルのライブラリを使用することもできます。

于 2012-07-01T07:17:31.197 に答える
3

LU RD による回答のサンプル コードが複雑すぎて好みに合わない場合は、.net BackgroundWorker クラスの Delphi 実装の方が好みに合うかもしれません。

OnWorkこれを使用して、コンポーネントをフォームにドロップし、さまざまなイベント ( 、OnWorkProgressOnWorkFeedbackおよび)のハンドラーを追加できますOnWorkComplete。コンポーネントはOnWork、GUI スレッドから他のイベント ハンドラーを実行しながら、バックグラウンドでイベント ハンドラーを実行します (必要なコンテキスト スイッチと同期を処理します)。OnWorkただし、イベント ハンドラーでコードを記述するには、セカンダリ スレッドから実行できることと実行してはいけないことを十分に理解する必要があります。

于 2012-07-01T17:29:15.227 に答える
0

マルチスレッドの有用な入門書は、Martin Harvey という人物によって何年も前に書かれました。彼のチュートリアルはEmbarcadero CC サイトで見つけることができます。また、探している種類のことを行うサンプル クラスをアップロードしたようですが、私はそれを見ていないので、確かなことは言えません。

于 2012-07-01T10:42:48.193 に答える