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

后端 未结 4 1984
渐次进展
渐次进展 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:39

    If the sample code in the answer by LU RD is too complicated for your taste then maybe a Delphi implementation of the .net BackgroundWorker class is more to your liking.

    Using this you can drop a component onto your form and add handlers for its various events (OnWork, OnWorkProgress, OnWorkFeedback and OnWorkComplete). The component will execute the OnWork event handler in the background, while executing the other event handlers from the GUI thread (taking care of the necessary context switches and synchronization). However, a thorough understanding of what you can and what you must not do from secondary threads is still necessary for writing code in the OnWork event handler.

    0 讨论(0)
  • 2020-12-08 01:42

    A useful introduction to multithreading was written by a guy called Martin Harvey, many years ago. His tutorial can be found at the Embarcadero CC site - it also looks like he has uploaded an example class which does the kind of thing you are looking for, but I haven't looked at it so cannot say for sure.

    0 讨论(0)
  • 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
    
    0 讨论(0)
  • 2020-12-08 01:53

    You can also use higher level libraries for threading, like:

    • http://andy.jgknet.de/blog/bugfix-units/asynccalls-29-asynchronous-function-calls/
    • http://otl.17slon.com/
    0 讨论(0)
提交回复
热议问题