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

gorkyyrv  于 2022-09-21  发布在  其他
关注(0)|答案(3)|浏览(403)

我有一个Delphi应用程序,它在一些TTimer.OnTimer事件上产生6个匿名线程。

如果我从标题栏中的X按钮关闭应用程序,将引发地址$C0000005处的访问冲突,并且FastMM报告泄漏的TAnomousThread对象。

释放Delphi中使用TThread.CreateAnonymousThread()方法在OnTimer事件中创建的匿名线程的最佳方式是什么?

对我有效的解决方案:

创建了匿名线程的 Package 器,该 Package 器在被释放时终止它们。

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;

这里的结尾是你可以这样称呼它:

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;

没有内存泄漏,关机正常,使用方便。

oyt4ldly

oyt4ldly1#

如果您想要维护和控制线程的生存期,那么它必须将FreeOnTerminate设置为False。否则,在线程开始执行后引用它是错误的。这是因为一旦它开始执行,您没有现成的方法知道它是否已被释放。

CreateAnonymousThread的调用创建了一个将FreeOnTerminate设置为True的线程。
该线程还被标记为FreeOnTerminate,因此在调用Start之后不应接触返回的示例。

因此,但默认情况下,您无法对线程的生命周期施加控制。但是,您可以在调用Start之前将FreeOnTerminate设置为False。就像这样:

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

然而,我不确定我会不会那样做。CreateAnonymousThread的设计是线程在终止时自动释放。我个人认为,我要么遵循预期的设计,要么衍生出我自己的TThread后代。

nx7onnlm

nx7onnlm2#

要避免使用CreateAnonymousThread出错,只需在启动之前将FreeOnTerminate设置为False即可。

这样,您就可以像往常一样使用线程,而无需任何变通方法。

您可以阅读说明CreateAnonymousThread自动将FreeOnTerminate设置为True的文档,这就是在引用线程时导致错误的原因。

5m1hhzi4

5m1hhzi43#

让您的线程关注来自外部的某种通知。这可能是发出信号的事件、发送到线程拥有的窗口的消息、通过您的线程侦听的套接字发送的命令或您找到的任何其他通信形式。

如果您确定这个问题是因为您的线程是所谓的“匿名”线程,那么一个简单的解决方法就是使它们成为非匿名线程。将匿名函数体放入Execute方法,并通过其构造函数将任何捕获的变量传递给线程类。

相关问题