c 00000005 ACCESS_VIOLATION在以下线程运行时关闭表单(退出应用程序)时导致(这只是一个示例,用于说明问题):
type
Twtf = class(TThread)
protected
procedure Execute; override;
end;
procedure Twtf.Execute;
const
hopefully_big_enough_to_trigger_problem = 100000000;
var
A: array of Integer;
I: Integer;
begin
SetLength(A, hopefully_big_enough_to_trigger_problem);
while not Terminated do begin
for I := Low(A) to High(A) do
A[I] := 0;
end;
end;
var
wtf: Twtf;
procedure TForm1.Button4Click(Sender: TObject);
begin
wtf := Twtf.Create;
wtf.FreeOnTerminate := True;
end;
根据Local Variables调试窗口,A
现在是()
,即Length(A)是0。显然,这就是访问A[I]
会导致问题的原因,但请注意,我没有手动对A
执行任何操作。
我如何停止显示这个错误(不一定要防止发生)?应用程序正在关闭,线程应该只是死...安静。这实际上发生在较小的数组,但有一个足够大的1或2每3次尝试导致问题(如果运行调试它总是显示;即这总是发生,但有时简单地隐藏)。
以下内容没有帮助:
destructor TForm1.Destroy;
begin
wtf.terminate;
inherited;
end;
相反,只在析构函数中执行TerminateThread(wtf.Handle, 0);
就解决(隐藏)了问题,但肯定有更优雅的方法吗?
2条答案
按热度按时间wsewodh21#
一旦你启动了自毁线程,你就严重限制了你管理它和执行所需的清理和等待的能力。它们作为需要运行相当小的任务的"发射和遗忘"线程是很方便的,而这些任务可以在任何时候被安全地中断(杀死)。
如果任务需要清理,则不应使用自销毁线程。
除了在应用程序关闭期间发生的AV之外,您的代码还存在其他问题。
以下代码不是初始化自毁线程的正确方法:
在上面的代码中,线程将在构造后立即开始运行,并且有可能(尽管不太可能)线程将在您将
FreeOnTerminate
标志设置为true之前完成其工作,这将导致内存泄漏。正确的方法是在挂起模式下创建线程,然后设置
FreeOnTerminate
,然后启动线程:另一种方法是重写
TThread.Create
并在那里设置FreeOnTerminate
标志。下一个问题是在你启动了自销毁线程之后,你不应该访问它的引用,所以你不能调用
wtf.Terminate
,也不能调用TerminateThread(wtf.Handle, 0);
,因为在那时线程可能已经被销毁了,wtf
将是指向不存在示例的悬空引用。因为不能调用
wtf.Terminate
,所以while not Terminated
循环也是无用的,除非您要构造该线程类的其他示例,并且要手动管理这些示例。有许多方法可以解决您的问题,但您将选择哪一种方法取决于线程正在执行的实际工作、您需要运行多少这样的线程以及您是否可以自由中断(终止)它们或需要等待它们完成。
如果您可以安全地中断正在执行的线程,并且显示AV是您唯一的问题,那么您可以使用
try...except
PackageExecute
方法中的代码,并吃掉异常。最好的选择是使用手动管理的线程,您将创建一次,并在窗体关闭时销毁。在这种情况下,
while not Terminated
循环是有意义的,因为它将允许线程在应用程序关闭时中断。您也可以在应用程序关闭过程的早期调用wtf.Terminate
(例如,在OnClose
中,或OnCloseQuery
以给予线程更多时间来检查Terminated
标志。当然,还有其他管理线程的方法,不管它们是自毁的还是手动管理的,但是不可能把它们全部列出,特别是当你的特定用例不清楚的时候,但是所有这些方法都需要一些额外的机制来通知线程它需要停止它的工作,或者通知应用程序有线程仍然在运行。
根据您使用的Delphi版本,您可能还想查看
TTask
,因为运行和处理任务比处理线程更容易,并且用于运行任务的线程是由RTL自动管理的。如果您真的、真的、真的坚持使用自毁线程,并且只想在uses关闭应用程序后尽快终止所有线程,那么您可以使用
TerminateProcess
。请注意,如果您的应用程序使用DLL或共享内存,则通过调用TerminateThread
、ExitProcess
、TerminateProcess
突然终止线程或进程可能会导致问题。请参见:TerminateProcess function和Terminating a Process要做到这一点,您可以在主窗体中添加以下析构函数:
补充阅读:
How to wait that all anonymous thread are terminated before closing the app?
Can I call TerminateThread from a VCL thread?
k4aesqcs2#
重申:
此示例旨在说明在应用程序终止时线程循环正在访问动态数组时会发生什么情况(无论采用何种正常关闭机制,线程都可能在该时间点处于循环中,并且阻塞应用程序,等待它完成或进入
if Terminated
检查可能需要很长时间,这显然不是最佳选择)。这个问题是由于应用程序在释放线程之前释放了数组的内存,而线程正在处理数组(数组是属于线程的变量);例如对于足够大的多维
A: array of array of Integer
,实际上可以看到A[500000]
被释放到()
,而A[400000]
仍然良好。解决办法是如此简单,以至于微不足道:
这消除了处理其数组的线程与释放该数组的应用程序之间的争用情况。
以上所有都与
FreeOnTerminate
无关。编辑:添加
Finished
检查EDIT:弃用暂停的替代选项:
SuspendThread(Handle);