Do I need TThreads? If so can I pause, resume and stop them?

后端 未结 4 1985
渐次进展
渐次进展 2020-12-08 01:00

I\'ve always wondered is there a better way that I should be writing some of my procedures, particularly ones that take a long time to finish.

I have always run ever

4条回答
  •  一生所求
    2020-12-08 01:50

    Yes, this is definitely a case where you need a thread to do the task.

    A little example how to pause/resume a thread and cancel the thread.

    Progress is sent to the main thread through a PostMessage call. The pause/resume and cancel are made with TSimpleEvent signals.

    Edit: As per the comments from @mghie, here is a more complete example:

    Edit 2: Showing how to pass a procedure for the thread to call for the heavy work.

    Edit 3: Added some more features and a test unit.

    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.
    

    Just call MyThread.Paused := true to pause and MyThread.Paused := false to resume the thread operation.

    To cancel the thread, call MyThread.Free.

    To receive the posted messages from the thread, see following example:

    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.
    

    And the form:

    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
    

提交回复
热议问题