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

时间:2012-05-01 17:47:34

标签: multithreading delphi delphi-xe2 anonymous terminate

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

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

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

解决方案对我有用:

创建了一个匿名线程的包装器,在被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;

这里结束的是你如何称呼它:

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;

没有内存泄漏,正常关机且易于使用。

3 个答案:

答案 0 :(得分:15)

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

CreateAnonymousThread的调用会创建一个FreeOnTerminate设置为True的主题。

  

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

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

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

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

答案 1 :(得分:9)

使用CreateAnonymousThread避免错误,只需在启动前将FreeOnTerminate设置为False

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

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

答案 2 :(得分:3)

让你的线程从外面注意某种通知。这可能是一个发出信号的事件,一个发送到线程拥有的窗口的消息,一个通过线程监听的套接字发送的命令,或者你找到的任何其他形式的通信。

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