When to Free a Thread manually

前端 未结 1 1914
不思量自难忘°
不思量自难忘° 2020-12-18 00:05

If I create a (suspended) thread from the main thread as such:

  with TMyThread.Create(True) do
  begin
    OnTerminate := ThreadTerminated;
    FreeOnTermin         


        
相关标签:
1条回答
  • 2020-12-18 00:39

    Obviously, somewhere there has to be a reference to the instantiated thread. But I can relate to your wish: you want a always-done-never-care solution.

    I suggest you manage the thread's existence by a separate ThreadController class:

    unit Unit2;
    
    interface
    
    uses
      Classes, SysUtils, Forms, Windows, Messages;
    
    type
      TMyThreadProgressEvent = procedure(Value: Integer;
        Proceed: Boolean) of object;
    
    procedure RunMyThread(StartValue: Integer; OnProgress: TMyThreadProgressEvent);
    
    implementation
    
    type
      TMyThread = class(TThread)
      private
        FException: Exception;
        FOnProgress: TMyThreadProgressEvent;
        FProceed: Boolean;
        FValue: Integer;
        procedure DoProgress;
        procedure HandleException;
        procedure ShowException;
      protected
        procedure Execute; override;
      end;
    
      TMyThreadController = class(TObject)
      private
        FThreads: TList;
        procedure StartThread(StartValue: Integer;
          OnProgress: TMyThreadProgressEvent);
        procedure ThreadTerminate(Sender: TObject);
      public
        constructor Create;
        destructor Destroy; override;
      end;
    
    var
      FMyThreadController: TMyThreadController;
    
    function MyThreadController: TMyThreadController;
    begin
      if not Assigned(FMyThreadController) then
        FMyThreadController := TMyThreadController.Create;
      Result := FMyThreadController
    end;
    
    procedure RunMyThread(StartValue: Integer; OnProgress: TMyThreadProgressEvent);
    begin
      MyThreadController.StartThread(StartValue, OnProgress);
    end;
    
    { TMyThreadController }
    
    constructor TMyThreadController.Create;
    begin
      inherited;
      FThreads := TList.Create;
    end;
    
    destructor TMyThreadController.Destroy;
    var
      Thread: TThread;
    begin
      while FThreads.Count > 0 do
      begin
        Thread := FThreads[0]; //Save reference because Terminate indirectly
                               //extracts the list entry in OnTerminate!
        Thread.Terminate; //Indirectly decreases FThreads.Count
        Thread.Free;
      end;
      FThreads.Free;
      inherited Destroy;
    end;
    
    procedure TMyThreadController.StartThread(StartValue: Integer;
      OnProgress: TMyThreadProgressEvent);
    var
      Thread: TMyThread;
    begin
      Thread := TMyThread.Create(True);
      FThreads.Add(Thread); //Add to list before a call to Resume because once
                            //resumed, the thread might be gone already!
      Thread.FValue := StartValue;
      Thread.FOnProgress := OnProgress;
      Thread.OnTerminate := ThreadTerminate;
      Thread.Resume;
    end;
    
    procedure TMyThreadController.ThreadTerminate(Sender: TObject);
    begin
      FThreads.Extract(Sender);
    end;
    
    { TMyThread }
    
    procedure TMyThread.DoProgress;
    begin
      if (not Application.Terminated) and Assigned(FOnProgress) then
        FOnProgress(FValue, FProceed);
    end;
    
    procedure TMyThread.Execute;
    begin
      try
        FProceed := True;
        while (not Terminated) and (not Application.Terminated) and FProceed and
          (FValue < 20) do
        begin
          Synchronize(DoProgress);
          if not FProceed then
            Break;
          Inc(FValue);
          Sleep(2000);
        end;
        //In case of normal execution ending, the thread may free itself. Otherwise,
        //the thread controller object frees the thread.
        if not Terminated then
          FreeOnTerminate := True;
      except
        HandleException;
      end;
    end;
    
    procedure TMyThread.HandleException;
    begin
      FException := Exception(ExceptObject);
      try
        if not (FException is EAbort) then
          Synchronize(ShowException);
      finally
        FException := nil;
      end;
    end;
    
    procedure TMyThread.ShowException;
    begin
      if GetCapture <> 0 then
        SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
      if (FException is Exception) and (not Application.Terminated) then
        Application.ShowException(FException)
      else
        SysUtils.ShowException(FException, nil);
    end;
    
    initialization
    
    finalization
      FreeAndNil(FMyThreadController);
    
    end.
    

    To run this sample thread which counts from 5 to 19 in 2 second intervals and provides feedback and an opportunity to a premature termination, call from the main thread:

    type
      TForm1 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
      private
        procedure MyThreadProgress(Value: Integer; Proceed: Boolean);
      end;
    
    ...
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      RunMyThread(5, MyThreadProgress);
    end;
    
    procedure TForm1.MyThreadProgress(Value: Integer; Proceed: Boolean);
    begin
      Caption := IntToStr(Value);
    end;
    

    This thread automatically kills itself on either thread's or application's termination.

    Maybe this unit is a little overkill for your situation because it is capable of handling multiple threads (of the same type), but I think it answers your question. Adjust to your liking.

    Partial origin of this answer: NLDelphi.com.

    0 讨论(0)
提交回复
热议问题