且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何终止匿名线程在Delphi中的应用程序关闭?

更新时间:2022-10-25 14:17:19

如果要维护并对线程的生命周期进行控制,则必须将 FreeOnTerminate 设置为 False 。否则,在线程开始执行后引用线程是一个错误。这是因为一旦它开始执行,你没有准备好的方法来知道它是否被释放。



调用 CreateAnonymousThread 创建一个 FreeOnTerminate 设置为 True


线程也被标记为FreeOnTerminate,所以在调用Start后不应该触摸返回的实例。


所以,但默认情况下,你无法对线程的生命周期进行控制。但是,您可以在调用开始之前将 FreeOnTerminate 设置为 False 开始。像这样:

  MyThread:= TThread.CreateAnonymousThread(MyProc); 
MyThread.FreeOnTerminate:= False;
MyThread.Start;

但是,我不知道我会这样做。 CreateAnonymousThread 的设计是线程在终止后自动释放。我想我个人会按照预期的设计,或者派生我自己的 TThread descendent。


I have a Delphi application which spawns 6 anonymous threads upon some TTimer.OnTimer event.

If I close the application from the X button in titlebar Access Violation at address $C0000005 is raised and FastMM reports leaked TAnonymousThread objects.

Which is the best way to free anonymous threads in Delphi created within OnTimer event with TThread.CreateAnonymousThread() method?

SOLUTION which worked for me:

Created a wrapper of the anonymous threads which terminates them upon being Free-ed.

type
  TAnonumousThreadPool = class sealed(TObject)
  strict private
    FThreadList: TThreadList;
    procedure TerminateRunningThreads;
    procedure AnonumousThreadTerminate(Sender: TObject);
  public
    destructor Destroy; override; final;
    procedure Start(const Procs: array of TProc);
  end;

{ TAnonumousThreadPool }

procedure TAnonumousThreadPool.Start(const Procs: array of TProc);
var
  T: TThread;
  n: Integer;
begin
  TerminateRunningThreads;

  FThreadList := TThreadList.Create;
  FThreadList.Duplicates := TDuplicates.dupError;

  for n := Low(Procs) to High(Procs) do
  begin
    T := TThread.CreateAnonymousThread(Procs[n]);
    TThread.NameThreadForDebugging(AnsiString('Test thread N:' + IntToStr(n) + ' TID:'), T.ThreadID);
    T.OnTerminate := AnonumousThreadTerminate;
    T.FreeOnTerminate := true;
    FThreadList.LockList;
    try
      FThreadList.Add(T);
    finally
      FThreadList.UnlockList;
    end;
    T.Start;
  end;
end;

procedure TAnonumousThreadPool.AnonumousThreadTerminate(Sender: TObject);
begin
  FThreadList.LockList;
  try
    FThreadList.Remove((Sender as TThread));
  finally
    FThreadList.UnlockList;
  end;
end;

procedure TAnonumousThreadPool.TerminateRunningThreads;
var
  L: TList;
  T: TThread;
begin
  if not Assigned(FThreadList) then
    Exit;
  L := FThreadList.LockList;
  try
    while L.Count > 0 do
    begin
      T := TThread(L[0]);
      T.OnTerminate := nil;
      L.Remove(L[0]);
      T.FreeOnTerminate := False;
      T.Terminate;
      T.Free;
    end;
  finally
    FThreadList.UnlockList;
  end;
  FThreadList.Free;
end;

destructor TAnonumousThreadPool.Destroy;
begin
  TerminateRunningThreads;
  inherited;
end;

End here is how you can call it:

procedure TForm1.Button1Click(Sender: TObject);
begin
  FAnonymousThreadPool.Start([ // array of procedures to execute
    procedure{anonymous1}()
    var
      Http: THttpClient;
    begin
      Http := THttpClient.Create;
      try
        Http.CancelledCallback := function: Boolean
          begin
            Result := TThread.CurrentThread.CheckTerminated;
          end;
        Http.GetFile('http://mtgstudio.com/Screenshots/shot1.png', 'c:\1.jpg');
      finally
        Http.Free;
      end;
    end,

    procedure{anonymous2}()
    var
      Http: THttpClient;
    begin
      Http := THttpClient.Create;
      try
        Http.CancelledCallback := function: Boolean
          begin
            Result := TThread.CurrentThread.CheckTerminated;
          end;
        Http.GetFile('http://mtgstudio.com/Screenshots/shot2.png', 'c:\2.jpg');
      finally
        Http.Free;
      end;
    end
  ]);
end;

No memory leaks, proper shutdown and easy to use.

If you want to maintain and exert control over a thread's lifetimes then it must have FreeOnTerminate set to False. Otherwise it is an error to refer to the thread after it has started executing. That's because once it starts executing, you've no ready way to know whether or not it has been freed.

The call to CreateAnonymousThread creates a thread with FreeOnTerminate set to True.

The thread is also marked as FreeOnTerminate, so you should not touch the returned instance after calling Start.

And so, but default, you are in no position to exert control over the thread's lifetime. However, you could set FreeOnTerminate to False immediately before calling Start. Like this:

MyThread := TThread.CreateAnonymousThread(MyProc);
MyThread.FreeOnTerminate := False;
MyThread.Start;

However, I'm not sure I would do that. The design of CreateAnonymousThread is that the thread is automatically freed upon termination. I think I personally would either follow the intended design, or derive my own TThread descendent.