技巧:处理线程中的异常(50分)

  • 主题发起人 主题发起人 shenloqi
  • 开始时间 开始时间
S

shenloqi

Unregistered / Unconfirmed
GUEST, unregistred user!
线程中的异常会导致应用程序的异常终止,所以应当在线程中捕捉异常,并用另一种方式
把这个异常通知给应用程序的主线程。

如果你希望主线程接受实际的异常对象,而不只是捕捉异常消息(如果只是要显示消息,
只需要把消息字符串传递给主线程,然后在主线程中用对话框显示出来就可以了),就需
要做类似下面的处理工作。因为当一个try...except...end块完成异常处理时,Delphi会
自动释放这个异常对象,所以下面的工作就是截取异常对象,把它传递给主线程,并防止
Delphi过早的释放他(修改运行时堆栈上的异常帧来瞒过Delphi ^.* ),这个方法是将
线程过程包裹在try...except...end块之内,传递给线程函数的参数是指向对象引用的指
针(Param: Pointer),函数可以把一个异常对象存放在该对象引用上,如果成功则存放
nil。

代码:
//PExceptionRecord是Windows.pas中导入的
uses Windows;

type
  //指向TObject的指针
  PObject = ^TObject;
  //指向TRaiseFrame的指针
  PRaiseFrame = ^ TRaiseFrame;
  //记录类型的TRaiseFrame
  TRaiseFrame = record
    //RaiseList函数返回的是指针,所以NextFrame也应该是指针
    NextFrame: PRaiseFrame;
    ExceptAddr: Pointer;
    ExceptObject: TObject;
    ExceptionRecord: PExceptionRecord;
  end;

{-----------------------------------------------------------------------------
  Procedure: ThreadFunc
  Author:    slq
  Date:      22-三月-2002
  Arguments: Param: Pointer
  Result:    Integer
  Purpose:   展示如何在线程中捕获异常
             这个函数捕捉异常并把他们放到Param^中,
             如果不产生异常则返回nil。
-----------------------------------------------------------------------------}
function ThreadFunc(Param: Pointer): Integer;
var
  RaiseFrame:PRaiseFrame;
begin
  Result := 0;
  PObject(Param)^ := nil;
  try

    //这里是这个线程函数的主体执行代码
    //
    //TODO: HereIsRealWork

  except
    //如果RaiseList为nil表示没有异常,否则RaiseList返回指向
    //一个TExceptionFrame记录
    RaiseFrame := RaiseList;
    if RaiseFrame <> nil then
    begin
      //当线程抛出异常时,保存异常对象到RaiseFrame.ExceptObject,
      //设置这个异常对象的引用为nil,Delphi就不会过早的释放异常对象。
      //
      //这里涉及到引用计数方面的内容,需要对引用计数比较熟悉。
      //用类似的方法可以实现使得string等等Delphi原先自动引用计数
      //的类型不过早的释放以完成特殊的效果。
      PObject(Param)^ := RaiseFrame.ExceptObject;
      RaiseFrame.ExceptObject := nil;
    end;
  end;
end;
 
好像很麻烦吧,,
我处理线程的异常是直接在execute中加上
try
except
on E: Exception do
begin
G_Error:=E.message;
PostMessage(Frmmain.Handle, XE_Message, 0, 0)

end;
end;
讲线程中的异常给全局变量,然后再发消息给主窗体去处理罗。。。。
 
这种方法只能显示异常消息:)

我在前面的说明里面说了的。
如果你希望主线程接受实际的异常对象,而不只是捕捉异常消息(如果只是要显示消息,
只需要把消息字符串传递给主线程,然后在主线程中用对话框显示出来就可以了),就需
要做类似下面的处理工作。
 
呵呵,好像差别不大。。
主线程获取实际的异常对象,我们也是根据这个异常消息进行相关处理,
shenloqi:
能不能告诉我主线程获取实际的异常对象和获取异常消息的区别在哪里?
我是说具体意义,:)我理解的不是很透彻。。

 
我写了半天结果又没有能够保存,大富翁怎么了?!

再写一次:

我认为这两者之间还是有不少差别的:
异常消息的地址应该是异常对象的ExceptionRecord所指向的地址,并且Delphi里面抛
出的异常都是经由SysUtils单元映射过的。
我这种方法得到的异常对象是真正出错的异常对象,而不是预计可能会出错的对象,因
为Delphi中出错的时候有可能是其成员类的错误等等。

另外:我觉得在主线程中获得异常消息等主线程处理后再释放异常对象不如先瞒过Delphi
以为释放了异常对象,再在主线程中处理来的好,因为前者增加了程序运行的风险,如果
还是使用对话框的方式我觉得就更不佳了(当然,更差的是在对话框被用户处理之后再处
理出现异常遗留的现场)。
 
先看看再说话
 
To shenloqi:
还是看的不太明白,可否在详细些。听课:)
 
我真是屡教不改:(大富翁每次都要我输入两次以上才给我放到数据库:(

这样把,我这里解释一下RaiseList,等一会儿我看看有可能的话,就发一个关于自动
计数的东西吧。

RaiseList是一个真正的函数,他返回一个指向当前异常结构的指针,但是在System单元
中并没有声明这种类型的异常结构,这个指针可能是一个异常列表中的第一个节点。列
表中的第一个结构是异常。如果一个异常处理程序引发了一个异常,那么在异常列表中
可以有多个结构。对于每一个线程都有自己独立的异常列表,但是通常用不到这个函数,
因为try...except...end语句提供了处理异常的必要的功能。(Delphi直到现在的版本
还没有try...except...finally...end!!!我觉得很过分哪)
 
RaiseList是什么?在哪定义的?
而且你这样也没有及时通知主线程去处理每个线程的异常啊。
 
呵呵,发晚了一点。
 
RaiseList为什么没有帮助?真是的。
 
一般情况下对于多线程的程序,程序员会预计出现错误的情形并且给以处理方法,而程序
员在调试的时候也需要得到更多的错误信息来了解的更多。
实际上我们在except的处理部分里就可以加上原先你要做的对于异常的处理方法,只是使
用这个方法可以额外的获得异常对象。

我觉得线程中的异常不要等到主线程来解决,你说呢?
 
呵,听shenloqi讲了这么多,还是晕晕的,不怎么清楚,或者说还不懂得
应用,真是不好意思。。:))
 
in system.pas:

function RaiseList: Pointer;
asm
CALL SysInit.@GetTLS
MOV EAX, [EAX].RaiseListPtr
end;
 
多人接受答案了。
 
后退
顶部