当你跑起了一个异步线程,并用 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

转载于:https://www.cnblogs.com/walterlv/p/10236532.html

使用 ExceptionDispatchInfo 捕捉并重新抛出异常相关推荐

  1. 2019-11-29-dotnet-代码调试方法

    title author date CreateTime categories dotnet 代码调试方法 lindexi 2019-11-29 8:50:0 +0800 2019-6-5 9:4:4 ...

  2. 2019-10-28-dotnet-代码调试方法

    title author date CreateTime categories dotnet 代码调试方法 lindexi 2019-10-28 08:50:11 +0800 2019-6-5 9:4 ...

  3. java 同步块 抛出异常_java问题合集(一)

    垃圾回收算法 引用计数法,标记清除法,标记压缩清除法(Java中老年代采用),复制算法(Java中新生代采用),分代法(Java堆采用),分区算法. 重要的三句话: 垃圾回收器只知道释放那些经由new ...

  4. 【C#本质论 十二】异常处理

    年前最后一篇技术博客了,由于近期的上线,自学进度严重滞后,年后还是得拾起来啊,闲言少叙,书归正传,经过艰苦的学习奋斗,终于来到了中级部分知识的最后一篇内容<异常处理>,其实之前学习Java ...

  5. Java中如何锁文件

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | liululee 来源 | 公众号「锅外的大佬 ...

  6. Java中非法参数的异常_Java中的异常

    异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 过API中Throwable类的众多子类描述各种不同的异常. ...

  7. 深入理解java异常处理机制

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

  8. Java 异常(Java Exception)(一)

    Java异常 异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 过API中Throwable类的众多子类描述各 ...

  9. java 编写异常_Java基础编程之异常处理

    Java异常类是对于程序中可能出现的错误或者异常的一种处理方式.在设计程序的过程中,对于可能出现的异常错误,比如说用户输入错误,设备错误,磁盘满了或者代码错误等等,通常采用异常处理的方式来进行处理可能 ...

最新文章

  1. js onclick方法
  2. 小米一键上锁工具_小米智能门锁和猫眼,可女声变男声与访客隔门沟通
  3. 对话行癫:解密阿里云顶层设计和底层逻辑
  4. mysql输入错误怎样更正_HotDB MySQL 篇| MySQL 源码系列的补充与更正
  5. 单片机原理及其应用——单片机控制8只发光二极管交替闪烁
  6. spring-DataSource
  7. nodejs 创建一个静态资源服务器 +路由
  8. Received status code 403 from server: Forbidden
  9. svpwm矢量控制电机相电压波形_SVPWM调制中的6个非零基础电压矢量的幅值到底是Udc还是2/3Udc ? 电压利用率为什么是1?...
  10. 模仿iframe框架,由分隔栏动态改变左右两侧div大小———基于jQuery
  11. 试卷模板 html,试卷模板怎么转换a4Word
  12. js 实现选择文件存放路径
  13. iphone震动反馈怎么设置,怎么设置开启苹果手机ios12打字时的震动反馈
  14. 重磅宣布|强强联合,腾讯云携手Veeam提供云上数据存储服务
  15. Qt-android开发环境搭建及打包安装测试hello world
  16. springboot整合bboss操作elasticsearch
  17. ClusterStorage-5-配置ACLs与Quotas之设置ACLs
  18. c语言——常见占位符(格式说明)
  19. wps文档漫游删除_WPS自带的文档漫游和在线模板怎么关闭?
  20. 周鸿袆教你打造十页完美商业计划书的十条法则

热门文章

  1. (三)Appium-desktop 打包
  2. 好戏连台,BCH独领风骚
  3. servlet上传文件接收工具
  4. 《R语言编程艺术》——1.4 R语言中一些重要的数据结构
  5. iOS IM开发建议(一)App框架设计
  6. Sharepoint学习笔记—Ribbon系列-- 5. 在Ribbon中添加新控件(针对用户自定义Tab)
  7. java离职交接文档_定了!财会人离职不办这2项交接,要承担法律责任!后果非常严重...
  8. 全球链界科技发展大会_科技界女性占五席
  9. RTFM? 如何写一本值得一读的手册
  10. Bootstrap3 折叠插件的调用方式