9

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

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

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

对我有用的解决方案:

创建了匿名线程的包装器,在被释放后终止它们。

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;

没有内存泄漏,正确关闭且易于使用。

4

3 回答 3

16

如果您想维护和控制线程的生命周期,那么它必须FreeOnTerminate设置为False. 否则在线程开始执行后引用它是错误的。那是因为一旦它开始执行,你就无法知道它是否已经被释放了。

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

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

因此,默认情况下,您无法控制线程的生命周期。但是,您可以在调用 之前立即设置FreeOnTerminate为。像这样:FalseStart

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

但是,我不确定我会这样做。的设计CreateAnonymousThread是线程在终止时自动释放。我想我个人要么遵循预期的设计,要么派生出我自己的TThread后代。

于 2012-05-01T18:31:50.743 回答
9

为了避免在启动之前使用CreateAnonymousThread刚刚设置FreeOnTerminate的错误。False

这样您就可以像平常一样使用线程,而无需任何解决方法。

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

于 2013-03-10T23:54:25.093 回答
3

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

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

于 2012-05-01T18:28:54.077 回答