Multithreading Delphi线程异常机制
在delphi中线程如何工作,以及为什么在线程应该引发异常的时候,异常却没有显示出来,这让我左右为难。下面是带注释的代码,也许有人可以向我解释一下线程或delphi是如何管理访问冲突的 //线程代码Multithreading Delphi线程异常机制,multithreading,delphi,exception-handling,Multithreading,Delphi,Exception Handling,在delphi中线程如何工作,以及为什么在线程应该引发异常的时候,异常却没有显示出来,这让我左右为难。下面是带注释的代码,也许有人可以向我解释一下线程或delphi是如何管理访问冲突的 //线程代码 unit Unit2; interface uses Classes, Dialogs, SysUtils, StdCtrls; type TTest = class(TThread) private protected j: Integer; pr
unit Unit2;
interface
uses
Classes,
Dialogs,
SysUtils,
StdCtrls;
type
TTest = class(TThread)
private
protected
j: Integer;
procedure Execute; override;
procedure setNr;
public
aBtn: tbutton;
end;
implementation
{ TTest }
procedure TTest.Execute;
var
i : Integer;
a : TStringList;
begin
// make severals operations only for having something to do
j := 0;
for i := 0 to 100000000 do
j := j + 1;
for i := 0 to 100000000 do
j := j + 1;
for i := 0 to 100000000 do
j := j + 1;
for i := 0 to 100000000 do
j := j + 1;
for i := 0 to 100000000 do
j := j + 1;
for i := 0 to 100000000 do
j := j + 1;
for i := 0 to 100000000 do
j := j + 1;
for i := 0 to 100000000 do
j := j + 1;
Synchronize(setnr);
a[2] := 'dbwdbkbckbk'; //this should raise an AV!!!!!!
end;
procedure TTest.setNr;
begin
aBtn.Caption := IntToStr(j)
end;
end.
项目代码
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,
Unit2, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
public
nrthd:Integer;
acrit:TRTLCriticalSection;
procedure bla();
procedure bla1();
function bla2():boolean;
procedure onterm(Sender:TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.bla;
begin
try
bla1;
except on e:Exception do
ShowMessage('bla '+e.Message);
end;
end;
procedure TForm1.bla1;
begin
try
bla2
except on e:Exception do
ShowMessage('bla1 '+e.Message);
end;
end;
function TForm1.bla2: boolean;
var ath:TTest;
begin
try
ath:=TTest.Create(true);
InterlockedIncrement(nrthd);
ath.FreeOnTerminate:=True;
ath.aBtn:=Button1;
ath.OnTerminate:=onterm;
ath.Resume;
except on e:Exception do
ShowMessage('bla2 '+e.Message);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
//
try
bla;
while nrthd>0 do
Application.ProcessMessages;
except on e:Exception do
ShowMessage('Button1Click '+e.Message);
end;
ShowMessage('done with this');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
nrthd:=0;
end;
procedure TForm1.onterm(Sender: TObject);
begin
InterlockedDecrement(nrthd)
end;
end.
此应用程序的目的只是了解访问冲突被捕获的位置,以及应如何编写代码。我不明白为什么在“a[2]:='dbwdbkbckbk';”行中没有引发AV 线程是一个你应该接受异常的地方。 在线程中处理异常的要点是,如果希望将异常显示给最终用户,则应捕获异常并将其传递给主线程,以便安全地显示异常 您将在这个EDN线程中找到一些示例
程序TMyThread.DoHandleException;
开始
//取消鼠标捕获
如果GetCapture为0,则发送消息(GetCapture,WM_CANCELMODE,0,0);
//现在实际显示异常
如果feexception是Exception,那么
Application.ShowException(feException)
其他的
SysUtils.ShowException(feException,nil);
结束;
程序TMyThread.Execute;
开始
FEException:=零;
尝试
//提出例外
创建('我引发了一个异常');
除了
手部异常;
结束;
结束;
程序TMyThread.HandleException;
开始
//此函数是虚拟的,因此您可以覆盖它
//并添加您自己的功能。
FEException:=异常(ExceptObject);
尝试
//不显示EAPort消息
如果不是(feexception是EAbort),则
同步(DoHandleException);
最后
FEException:=零;
结束;
结束;
在Delphi 2005中-可能还有大多数其他版本-如果异常未经处理就从Execute
方法中逃逸,那么它将被调用Execute
的函数捕获并存储在线程的FatalException
属性中。(查看Classes.pas,ThreadProc
)在释放线程之前,不会对该异常执行任何进一步的操作,此时该异常也会被释放
因此,您有责任检查该财产并对其采取措施。您可以在线程的OnTerminate
处理程序中检查它。如果它不为null,那么线程将由于未捕获的异常而终止。例如:
procedure TForm1.onterm(Sender: TObject);
var
ex: TObject;
begin
Assert(Sender is TThread);
ex := TThread(Sender).FatalException;
if Assigned(ex) then begin
// Thread terminated due to an exception
if ex is Exception then
Application.ShowException(Exception(ex))
else
ShowMessage(ex.ClassName);
end else begin
// Thread terminated cleanly
end;
Dec(nrthd);
end;
没有必要使用互锁函数来跟踪线程数。线程创建函数和终止处理程序始终在主线程的上下文中运行。普通旧的
Inc
和Dec
就足够了。我们也可以重新提高FatalException。重新启动似乎不符合逻辑,但如果您的代码中有一个中央异常/错误处理程序,并且您只想将线程异常包括在该机制中,则可以在一些罕见的情况下重新启动:
procedure TForm1.onterm(Sender: TObject);
var
ex: Exception;
begin
Assert(Sender is TThread);
ex := Exception(TThread(Sender).FatalException);
if Assigned(ex) then
// Thread terminated due to an exception
raise ex;
Dec(nrthd);
end;
“a”变量未初始化!它可以指向计算机中任何可能的内存位置。它甚至可以指向物理上不存在的位置(尽管由于虚拟内存系统,这是没有意义的)
因此,在您的程序中,如果“a”意外地指向一个有效的内存地址(我指的是进程拥有的地址),那么您的代码将在该位置写入,而不会发生访问冲突。我认为你至少应该把a改为零
请参见Remy Lebeau的评论:
这也是:调试器没有告诉您异常情况吗?a变量没有初始化!如果它指向一个有效的内存地址怎么办?我指的是流程拥有的地址。然后,您的代码将在该位置写入,而不会发生访问冲突。我说得对吗?我认为你至少应该把a改为零。请看Remy Lebeau的评论。它解释了什么是AV以及它是如何发生的:+1。我从来没见过肥胖感?。。。哦,等等,我们还在使用Delphi5。我再也没有该版本的源代码了,@Lieven。如果它有
AcquireExceptionObject
,那么您可以自己模拟新的FatalException
行为。@Lieven:在D5和D6中,我认为线程的执行方法还没有受到保护。。。您必须在Execute的重写中自己执行。D6中引入了TThread.FatalException
属性(以及System.AcquireExceptionObject()
函数)。在D5中,Execute()
仅由try..finally
包装,以确保始终调用DoTerminate()
,但在将引发的异常发送到finally
之外的处理程序之前,线程被终止(通过EndThread()
)。在D6中,TThread.Execute()
由一个try..包装,除了在调用DoTerminate()
之前获取异常并将其存储在FatalException
中的
之外。在Delphi 10.1 Berlinar上工作。是否确实有效?释放那个异常对象怎么样?这不起作用(至少在XE2中是这样)。我尝试了它,得到了一个非模式消息对话框,后面是一个错误。OnTerminate
处理程序在Execute()
退出后由工作线程上下文中的Synchronize()
调用。如果在Synchronize()
中引发异常,则它将获取该异常并在工作线程上下文中重新引发它。因此,在OnTerminate
中引发异常是一件坏事。要正确执行此操作,您必须手动获取FatalException
的所有权,以某种方式将TThread.ffataleException
重置为nil(以便TThread
不会销毁对象),并在退出Synchronize()
后自己在主线程中重新引发它。黑客必须拥有所有权
procedure TForm1.onterm(Sender: TObject);
var
ex: Exception;
begin
Assert(Sender is TThread);
ex := Exception(TThread(Sender).FatalException);
if Assigned(ex) then
// Thread terminated due to an exception
raise ex;
Dec(nrthd);
end;