我有一个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;
没有内存泄漏,正常关机且易于使用。
答案 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
方法,并通过其构造函数将任何捕获的变量传递给线程类。