Welcome

首页 / 软件开发 / .NET编程技术 / CLR全面透彻解析 - CLR中未处理异常的处置

CLR全面透彻解析 - CLR中未处理异常的处置2010-12-02 MSDN Gaurav Khanna目录

托管异常处理

线程基础和未处理托管异常

CLR 创 建的线程的未处理托管异常

非 CLR 创建的线程的未处理异常

未 处理异常处置

AppDomain.UnhandledException 事件通知

未来关注点

不应该将未处理异常的处置神秘化。实际上,了解在此过程中所发 生的一切是非常有用的,因为它可以使崩溃的应用程序能够根据最后一刻的日志 内容进行诊断,以确定究竟是哪里出现了问题。此诊断信息非常有价值,可使您 节省确定故障所需的时间。

那么,什么是未处理异常处置呢?它是常规 异常处理机制的一个阶段,只有在对线程的所有堆栈帧都执行了异常处理程序搜 索但仍未发现任何异常后,才会针对异常触发这一机制。

在这里,我将 介绍对于托管异常的未处理异常处置,CLR 是如何实现的。但是,在进行详细介 绍之前,我们要先看一看托管异常处置通常是如何工作的。请注意,这里假设您 对 Windows® 结构化异常处理 (SEH) 机制以及相关的概念都已经非常熟悉 。要了解更多相关信息,您可以参考 Matt Pietrek 撰写的一篇非常不错的文章 "A Crash Course on the Depths of Win32 Structured Exception Handling",网址为 microsoft.com/msj/0197/Exception/Exception.aspx 。

托管异常处理

托管编程模型使用异常概念来通知堆栈上层的调 用方有关运行时的错误情况。通常,某个方法的调用方会很清楚此方法可能抛出 (或引发)的异常类型,因此会相应地从带有关联 catch 块的 try 块范围内或 带有处理程序块的托管筛选器内进行调用。

如果托管代码抛出异常(或 引发异步异常,如同不安全托管代码中的访问冲突一样),CLR 将开始从它找到 的与异常点最接近的第一个托管帧来遍历托管堆栈,并开始查找托管异常处理程 序。

除非另有说明,否则堆栈将减少,每行代表一个托管帧,它会调用 其下面的帧。如果托管帧调用本机帧,则本机帧将被显式调出。最顶端的帧是入 口点帧,最底端的帧是最近执行代码的帧。

图 1 是一个此类调用堆栈示 例。在这里,Bar 抛出了一个异常,这使得 CLR 开始从 Bar 遍历托管堆栈并一 直到 Main,同时查找异常处理程序。在本例中,Main 充当有问题的线程的入口 点托管帧。

图 1 Bar 抛出异常

图 2 中的图表显示了另一个调用堆栈,其中 Bar 中 的托管代码将使用 P/Invoke 来调用本机函数 Nfunc,此函数将抛出一个异常( 例如,抛出 C++ 异常)。由于异常发生在本机代码内,因此 CLR 不会意识到它 的存在,除非该异常没有被本机函数捕获,并进而进入托管 Bar 方法。在这种 情况下,CLR 将创建与本机异常相对应的托管异常对象,并会从包含 Bar 的帧 开始查找异常处理程序,一直到 Main 方法。

图 2 Nfunc 抛出异常

在这两个示例中,异常处理程序可以是根据类型匹 配规则与抛出异常的托管类型相匹配的 catch 块,也可以是同意在对 CLR 传递 过来的异常对象进行检查之后对异常进行处理的托管筛选器。

对于此示 例,我们假定 Main 有一个 catch 块来处理异常。在发现异常之后但在其内部 恢复执行之前,CLR 将发起异常解除操作(在 SEH 术语中也称为第二阶段), 从引发异常的点一直到同意处理异常的异常处理程序之前的一点来调用 finally 子句。对于托管帧,这还会导致对 fault 子句的调用(如果它们存在)。有关 fault 子句和托管异常通常处理的具体信息,可参阅 ECMA 335 规范(请参见 go.microsoft.com/fwlink/?LinkId=121873)。

在一些典型情况下, finally/fault 块会执行并完成所需的清理工作。所有此类块被执行完毕后, CLR 会继续在同意处理异常的 catch 块(或托管筛选器的处理程序块)中执行 。