我遇到一个尴尬的问题。在我的应用程序中我经常这样做
TThread.createAnonymousThread(
procedure
....
end).start
Run Code Online (Sandbox Code Playgroud)
我遇到的问题是,当我关闭应用程序的主窗体时,有时在 Tform.destroy 完成后,其中一些 AnonymousThread 仍然存在。他们是我的 Tform.destroy 中等待所有这些 AnonymousThread (在整个应用程序中各处创建的)成功终止后再继续的方法吗?
我发现这种方法可以列出所有正在运行的线程(来自How can I get a list with all the thread Created by my application):
program ListthreadsofProcess;
{$APPTYPE CONSOLE}
uses
PsAPI,
TlHelp32,
Windows,
SysUtils;
function GetTthreadsList(PID:Cardinal): Boolean;
var
SnapProcHandle: THandle;
NextProc : Boolean;
TThreadEntry : TThreadEntry32;
begin
SnapProcHandle := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); //Takes a snapshot of the all threads
Result := (SnapProcHandle <> INVALID_HANDLE_VALUE);
if Result then
try
TThreadEntry.dwSize := SizeOf(TThreadEntry);
NextProc := Thread32First(SnapProcHandle, TThreadEntry);//get the first Thread
while NextProc do
begin
if TThreadEntry.th32OwnerProcessID = PID then //Check the owner Pid against the PID requested
begin
Writeln('Thread ID '+inttohex(TThreadEntry.th32ThreadID,8));
Writeln('base priority '+inttostr(TThreadEntry.tpBasePri));
Writeln('');
end;
NextProc := Thread32Next(SnapProcHandle, TThreadEntry);//get the Next Thread
end;
finally
CloseHandle(SnapProcHandle);//Close the Handle
end;
end;
begin
{ TODO -oUser -cConsole Main : Insert code here }
GettthreadsList(GetCurrentProcessId); //get the PID of the current application
//GettthreadsList(5928);
Readln;
end.
Run Code Online (Sandbox Code Playgroud)
但看起来在这个列表中,它们的一些线程并不是真正由我的代码创建的,并且这些线程永远不会关闭。例如,对于空白项目,这是线程列表:
您面临的核心问题并非来自匿名线程本身,而是来自自毁匿名线程 - 已设置的匿名线程FreeOnTerminate。
为了等待线程,您需要引用线程或其句柄(Windows 平台)。因为您正在处理自毁线程,所以引用线程不是一个选项,因为一旦启动线程,您就不再允许触摸该引用。
Delphi RTL 在应用程序关闭期间不会对自毁匿名线程执行任何清理,因此在应用程序主窗体被破坏后,这些线程将被操作系统杀死,因此您的问题。
允许您等待匿名线程的解决方案之一,不需要任何复杂的内务处理和维护任何类型的列表,并且还需要对代码进行最小的更改(可以通过简单的查找和替换来完成),即TCountdownEvent使用计算线程数。
这需要替换TThread.CreateAnonymousThread为构造自定义线程类TAnoThread.Create(如果您愿意,您可以添加静态工厂方法,而不是直接调用构造函数),该类将具有与匿名线程相同的行为,除了它的实例将被计数并且您将能够等待所有此类线程完成运行。
type
TAnoThread = class(TThread)
protected
class var
fCountdown: TCountdownEvent;
class constructor ClassCreate;
class destructor ClassDestroy;
public
class procedure Shutdown; static;
class function WaitForAll(Timeout: Cardinal = INFINITE): TWaitResult; static;
protected
fProc: TProc;
procedure Execute; override;
public
constructor Create(const aProc: TProc);
end;
class constructor TAnoThread.ClassCreate;
begin
fCountdown := TCountdownEvent.Create(1);
end;
class destructor TAnoThread.ClassDestroy;
begin
fCountdown.Free;
end;
class procedure TAnoThread.Shutdown;
begin
fCountdown.Signal;
end;
class function TAnoThread.WaitForAll(Timeout: Cardinal): TWaitResult;
begin
Result := fCountdown.WaitFor(Timeout);
end;
constructor TAnoThread.Create(const aProc: TProc);
begin
inherited Create(True);
fProc := aProc;
FreeOnTerminate := True;
end;
procedure TAnoThread.Execute;
begin
if fCountdown.TryAddCount then
try
fProc;
finally
fCountdown.Signal;
end;
end;
Run Code Online (Sandbox Code Playgroud)
然后您可以在表单析构函数或任何其他适当的位置添加以下代码,并等待所有匿名线程完成运行。
destructor TForm1.Destroy;
begin
TAnoThread.Shutdown;
while TAnoThread.WaitForAll(100) <> wrSignaled do
CheckSynchronize;
inherited;
end;
Run Code Online (Sandbox Code Playgroud)
原理如下:倒计时事件以值 1 创建,当该值达到 0 时,将发出事件信号。要启动关闭,您可以调用Shutdown将减少初始计数的方法。您不能多次调用此方法,因为它会弄乱计数。
当匿名线程Execute方法启动时,它会首先尝试增加计数。如果它不能做到这一点,则意味着倒计时事件已发出信号,并且线程将终止而不调用其匿名方法,否则匿名方法将运行,并且在完成后计数将减少。
如果匿名线程使用TThread.Synchronize调用,则不能只调用WaitForAll,因为从主线程调用它会死锁。为了防止在等待线程完成时出现死锁,您需要调用CheckSynchronize来处理挂起的同步请求。
该解决方案对TAnoThread类的所有线程进行计数,无论它们是否自毁。这可以很容易地更改为仅计算那些已FreeOnTerminate设置的。
另外,当您调用Shutdown,并且仍然有一些正在运行的线程时,新线程仍然能够在此时启动,因为甚至没有发出倒计时信号。如果您想从那时起阻止新线程,您将需要添加一个布尔标志来指示您已启动关闭过程。