Delphi 线程异常机制

egmofgnx  于 2022-11-04  发布在  其他
关注(0)|答案(4)|浏览(216)

关于线程在 Delphi 中是如何工作的,我有一个两难的问题,为什么当线程应该引发异常时,异常却没有显示出来。
//线程代码

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不升高。

cfh9epnr

cfh9epnr1#

在 Delphi 2005中--可能还有大多数其他版本--如果一个异常从Execute方法中逃逸而没有被处理,那么它会被调用Execute的函数捕获,并存储在线程的FatalException属性中。(在 * Classes.pas *,ThreadProc中查找。)在线程被释放之前,不会对该异常执行任何操作,此时该异常也被释放。
因此,检查该属性并采取措施是您的责任。您可以在线程的OnTerminate处理程序中检查它。如果它为非空,则线程由于未捕获的异常而终止。因此,例如:

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;

不需要使用互锁函数来跟踪线程计数。线程创建函数和终止处理程序总是在主线程的上下文中运行。简单的IncDec就足够了。

jogvjijk

jogvjijk2#

线程是您应该接受异常的地方。
在线程中处理异常的要点是,如果您希望向最终用户显示异常,则应捕获它并将其传递到主线程,在主线程中可以安全地显示异常。
您可以在此EDN线程How to Handle exceptions in TThread Objects中找到一些示例。

procedure TMyThread.DoHandleException;
begin
  // Cancel the mouse capture
  if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
  // Now actually show the exception
  if FException is Exception then
    Application.ShowException(FException)
  else
    SysUtils.ShowException(FException, nil);
end;

procedure TMyThread.Execute;
begin
  FException := nil;
  try
    // raise an Exception
    raise Exception.Create('I raised an exception');
  except
    HandleException;
  end;
end;

procedure TMyThread.HandleException;
begin
  // This function is virtual so you can override it
  // and add your own functionality.
  FException := Exception(ExceptObject);
  try
    // Don't show EAbort messages
    if not (FException is EAbort) then
      Synchronize(DoHandleException);
  finally
    FException := nil;
  end;
end;
t40tm48m

t40tm48m3#

我们也可以重新引发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;
holgip5t

holgip5t4#

也许你展示的例子并不是最好的,因为变量“a”没有初始化!它可以指向你计算机中任何可能的内存位置。它甚至可以指向物理上不存在的位置(尽管由于虚拟内存系统,这是没有实际意义的)。
所以,在你的程序中,如果“a”意外地指向一个有效的内存地址(我指的是进程拥有的地址),那么你的代码将写入该位置而不会发生访问冲突。我认为你至少应该把“a”设置为NIL。在那之后,看看Lieven Keersmaekers的帖子。
请在此处查看雷米·勒博的评论:http://www.stackoverflow.com/a/16071764/46207
这也是:Why uninitialized pointers cause mem access violations close to 0?

相关问题