当你跑起了一个异步线程,并用 await 异步等待时,有没有好奇为什么能够在主线程 catch 到异步线程的异常?

当你希望在代码中提前收集好异常,最后一并把收集到的异常抛出的时候,能不能做到就像在原始异常发生的地方抛出一样?

本文介绍 ExceptionDispatchInfo,专门用于重新抛出异常。它在 .NET Framework 4.5 中首次引入,并原生在 .NET Core 和 .NET Standard 中得到支持。


先探索为什么需要重新抛出异常,再了解如何最佳地重新抛出异常。如果你只希望了解 ExceptionDispatchInfo,请直接从以下导航中点击跳转到最后一节。

 

重新抛出异常

说起重新抛出异常,你是否会认为就是写出如下代码?

try
{
DoButExceptionsMayOccur();
}
catch(Exception ex)
{
// 在这里进行抢救。
// 永远不要写出下面这句代码!(Don't write the code below forever!)
throw ex;
}

为了防止这段代码被意外复制出去危及项目,我特地在注释中标明了永远不应该直接写出 throw ex 这样的句子!

这是因为 throw 语句会为异常的实例填充调用栈信息,范围为 throw 的地方开始,到 catch 的地方结束。也就是说,在异常刚刚发生的时候,也就是 DoButExceptionsMayOccur 里面的某一个调用会成为调用栈的起点,上面写了 catch 所在的函数会成为调用栈的终点。然而,一旦在 catch 中写出了 throw ex 这样的语句,那么 ex 中的调用栈将会被重写,范围从这一句 throw 开始,到外面能 catch 的地方为止。

具体说来,假设上面那段代码出现在 Test 方法中,里面的 DoButExceptionsMayOccur 调用了方法 InnerInner 中发生了异常;而 Outer 调用了 Test 方法,Outer 中也 catch 了异常;即整个调用链为 Outer->Test->DoButExceptionsMayOccur->Inner。那么,当刚刚 catch 到异常时,ex 的调用栈为 Test->DoButExceptionsMayOccur->Inner,而如果写了 throw ex,那么 Outer 中将只能发现调用栈为 Outer->Test,丢失了内部真正出错的原因,这对诊断和修复异常非常不利!

如果只是为了解决上述文字中所说的问题,其实只需要去掉那个 ex 即可,即:

try
{
DoButExceptionsMayOccur();
}
catch(Exception)
{
// 在这里进行抢救。
throw;
}

然而,有时候这个异常并不直接从这里抛出(例如后台线程),或者说我们期望这是一个分步骤收集的异常(例如遍历)。这两种情况都有一个共同特点,就是重新抛出的地方根本就不在 catch 的地方。

后台线程的例子:

Exception exception = null;
DoSomething(() =>
{
// 这个 try-catch 块将在另一个线程执行。
try
{
DoButExceptionsMayOccur();
}
catch(Exception ex)
{
exception = ex;
}
});
if (exception != null)
{
// 重新抛出异常。
}

收集异常的例子:

List<Exception> exceptions = new List<Exception>();
foreach(var item in collection)
{
try
{
DoButExceptionsMayOccur(item);
}
catch(Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
{
// 重新抛出异常。
}

使用内部异常

.NET Framework 早期就提供了内部异常功能,专为解决保留调用栈而重新抛出异常而生。上面两段代码标记为// 重新抛出异常。的注释部分改为:

// 对应第一种情况。
throw new XxxException(ex);
// 对应第二种情况。
throw new AggregateException(exceptions);

于是两边的调用栈就被分别保留在了多个不同的 Exception 实例中。然而看异常总要一层层点开查看,始终不便。尤其是从产品中收集异常时,如何在异常分析系统中显示和分析也是个问题。

ExceptionDispatchInfo

如果将第一种情况写为:

ExceptionDispatchInfo.Capture(ex).Throw();

那么,这时外面的方法再 catch 异常,则会从外层直接看到里层,只在中间插入了一段文字,却看起来就像直接从原始出处抛出一样。

第二种情况写为:

if(exceptions.Count == 1)
{
ExceptionDispatchInfo.Capture(exceptions.First()).Throw();
}
else if(exceptions.Count > 1)
{
throw new AggregateException(exceptions);
}

使用这种方式,你看到的调用栈将是这样的:

至于多个异常的情况,那就只能使用内部异常来处理了。

而这些,正是 Task 管理异步线程异常时采用的策略——单个异常直接在调用线程直接抛出,多个异常抛出 AggregateException

使用 ExceptionDispatchInfo 捕捉并重新抛出异常的更多相关文章

  1. 2019-10-7-WPF-如何跨线程重新抛出异常

    title author date CreateTime categories WPF 如何跨线程重新抛出异常 lindexi 2019-10-07 13:24:54 +0800 2019-10-4 ...

  2. 应该抛出什么异常?不应该抛出什么异常?(.NET/C#)

    我在 .NET/C# 建议的异常处理原则 中描述了如何 catch 异常以及重新 throw.然而何时应该 throw 异常,以及应该 throw 什么异常呢? 究竟是谁错了? 代码中从上到下从里到外 ...

  3. 【C++】 C++异常捕捉和处理

    在阅读别人开发的项目中,也许你会经常看到了多处使用异常的代码,也许你也很少遇见使用异常处理的代码.那在什么时候该使用异常,又在什么时候不该使用异常呢?在学习完异常基本概念和语法之后,后面会有讲解. ( ...

  4. python虚拟机中的异常流控制

    异常:对程序运行中的非正常情况进行抽象.并且提供相应的语法结构和语义元素,使得程序员能够通过这些语法结构和语义元素来方便地描述异常发生时的行为. 1.Python中的异常机制: 1.1Python虚拟 ...

  5. Java之异常处理机制

    来源:深入理解java异常处理机制 2.Java异常    异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 ...

  6. [R]R语言里的异常处理与错误控制

    之前一直只是在写小程序脚本工具,几乎不会对异常和错误进行控制和处理. 随着脚本结构和逻辑更复杂,脚本输出结果的准确性验证困难,同时已发布脚本的维护也变得困难.所以也开始考虑引入异常处理和测试工具的事情 ...

  7. 再探Java基础——throw与throws

    http://blog.csdn.net/luoweifu/article/details/10721543 异常处理机制 异常处理是对可能出现的异常进行处理,以防止程序遇到异常时被卡死,处于一直等待 ...

  8. [swift]可选类型

    可选类型 <Swift权威指南>第2章千里之行始于足下——Swift语言基础,本章挑选了Swift语言的最基本特性加以介绍.尽管这些特性只占Swift全部特性的很少一部分,但却是所有的Sw ...

  9. 全面理解Java异常的运行机制

    1. 引子 try…catch…finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解.不过,我亲自体验的“教训”告诉我,这个东西可不是想象中的那么简单.听话. ...

随机推荐

  1. Delphi.format填充0

    1. ]);// 一共8位数字不够的补零 2. 3. 4. 5.

  2. apollo各协议支持的客户端

    apollo 源自 activemq,以快速.可靠著称,支持多协议:STOMP, AMQP, MQTT, Openwire, SSL, and WebSockets,下面就STOMP, AMQP, M ...

  3. 关于.net4.0中的Action委托

    在使用委托时,若封装的方法无返回值,并且参数在0-7个,可考虑使用.Net4.0中的Action委托,建议使用系统自带的,减少自定义 public delegate void Action<in ...

  4. CSS布局框架 960GS

    1.960GS 特点 小巧简单,功能单一(仅仅做排版的工作,其他东西靠自己.)(三个文件:reset.css,960.css,font.css) 界面宽960px,适合目前主流1/2以上显示器都满屏宽 ...

  5. 自已开发完美的触摸屏网页版仿app弹窗型滚动列表选择器/日期选择器

    手机端网页版app在使用下拉列表时,传统的下拉列表使用起来体验非常不好,一般做的稍好一点的交互功能界面都不会直接使用下拉列表,所以app的原生下拉列表都是弹窗列表选择,网页型app从使用体验上来当然也 ...

  6. IDEA的Tomcat配置Web的项目创建以及Servlet简单运行。

    相关软件: 1.IDEA编译器 2.JDK 3.Tomcat          (相关软件都可以到官网上下载,老表提示:不要下载最新版本因为不要做试验品)   IDEA的安装非常简单,找好安装的盘,n ...

  7. JS返回一个数据的千分位格式

    /** * 价钱转换-从右往左每3位数字加一个逗号 * @param price 需要转换的价格 */ formatPrice(price){ var newPrice = price.split(' ...

  8. iOS JavaScriptCore使用

    iOS JavaScriptCore使用 JavaScriptCore是iOS7引入的新功能,JavaScriptCore可以理解为一个浏览器的运行内核,使用JavaScriptCore可以使用nat ...

  9. http请求的GET和POST请求:查询和新增(server.php)

    <?php //设置页面内容是html编码格式是utf-8 header("Content-Type: text/plain;charset=utf-8"); //heade ...

  10. 基于IDEA和Maven的SSM分层项目搭建

    前言 虽然现在SpringBoot开始流行,但是SSM作为一个经典框架,还是有必要去了解一下. 项目建立 1.新建一个空白的Maven项目,如下图.然后把IDEA自动生成的多余src目录删掉. 2.右 ...