当你跑起了一个异步线程,并用 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. 随笔分类 - java高级特性

    转自:http://www.cnblogs.com/linjiqin/category/282013.html

  2. Python基础笔记系列八:字符串的运算和相关函数

    本系列教程供个人学习笔记使用,如果您要浏览可能需要其它编程语言基础(如C语言),why?因为我写得烂啊,只有我自己看得懂!! 字符串的运算1.字符串的加法和乘法 python中不但支持字符串相加,还支 ...

  3. 《用 Python 学微积分》笔记 3

    <用 Python 学微积分>原文见参考资料 1. 16.优化 用一个给定边长 4 的正方形来折一个没有盖的纸盒,设纸盒的底部边长为 l,则纸盒的高为 (4-l)/2,那么纸盒的体积为: ...

  4. Nginx 从0开始学

    作为一个 nginx 的初学者记录一下从零起步的点滴. 基本概念 Nginx 最常的用途是提供反向代理服务,那么什么反向代理呢?正向代理相信很多大陆同胞都在这片神奇的土地上用过了,原理大致如下图: 代 ...

  5. 解决msi文件在XP上安装未完成

    下载Ocra工具,然后删除"DIRCA_CheckFx"和"VSDCA_VsdLaunchConditions"这两个Action即可.第一步,下载并打开Ocr ...

  6. 毕业设计总结(1)-canvas画图

    去年6月底完成的毕业设计,到现在也才开始给它做个总结,里面有很多可以学习和借鉴的东西. 我的毕业设计的题目是“一种路径规划算法的改进与设计”,具体的要求可参见下面的表格: 题目 一种路径规划算法的改进 ...

  7. c中%

    %u 十进制无符号整数 %f 浮点数 %s 字符串 %c 单个字符 %p 指针的值 %e 指数形式的浮点数 %x, %X 无符号以十六进制表示的整数 %0 无符号以八进制表示的整数 %g 自动选择合适 ...

  8. model 数据注解

    https://www.cnblogs.com/leoxuan/articles/6555396.html ASP.NET MVC5中Model层开发,使用的数据注解有三个作用: 数据映射(把Mode ...

  9. wget下载指定目录下的文件

    wget -r -np -k -P  ~/tmp/    http://xxx.com/download -P 表示下载到哪个目录-r 表示递归下载-np 表示不下载旁站连接.-k 表示将下载的网页里 ...

  10. vuejs绑定img 的src

    1.显示本地图片: <img src="../../common/images/auth-icon.png" />   2.绑定变量: <img class=&q ...