.NET Core学习笔记(3)——async/await中的Exception处理
在写了很多年.NET程序之后,年长的猿类在面对异步编程时,仍不时会犯下致命错误,乃至被拖出去杀了祭天。本篇就async/await中的Exception处理进行讨论,为种族的繁衍生息做出贡献……
处理async/await中的Exception,最致命的莫过于想抓的Exception抓不到,程序崩的莫名其妙,连日志都没记下来,没法定位错误。让我们来看以下代码:
private async void SomethingWrongAsync()
{
await Task.Delay();
throw new InvalidOperationException();
} public void SomethingWrongCannotCatch()
{
try
{
SomethingWrongAsync();
}
catch (Exception)
{
// Sometimes we write log here, but the exception is never caught!
throw;
}
}
SomethingWrongAsync是一个标准的async方法。在这个方法中,我们主动抛出了InvalidOperationException。我们在方法SomethingWrongCannotCatch中调用了SomethingWrongAsync。但是非常遗憾,这里的try catch无法捕捉到InvalidOperationException。
包含以上代码的Sample工程是一个WPF程序,代码链接:
https://github.com/manupstairs/AsyncAwaitPractice
在测试之前,我们可以在throw那一行打个断点,F5起来后,点击MainWindow的SomethingWrongCannotCatch按钮。非常遗憾程序崩了,并且没有进入断点。

这意味着如果我们想在这个try catch里对Exception做出处理,甚至仅仅记录日志,都是一个不可能完成的任务。如果我们在WPF工程的App.xaml.cs里添加如下代码:
public partial class App : Application
{
public App()
{
this.DispatcherUnhandledException += (sender, e) =>
{
Debug.WriteLine(e);
};
}
}
确实是可以捕捉到这个异常,不过在DispatcherUnhandledException事件中,我们已经错过了处理Exception的时机,能做的也仅仅是记录日志。这并不是正确的处理异常的方式。
让我们来看另一段稍有不同的代码:
private async Task TaskWrongAsync()
{
await Task.Delay();
throw new InvalidOperationException();
} public void TaskWrongWithNothing()
{
try
{
TaskWrongAsync();
}
catch (Exception)
{
// Sometimes we write log here, but the exception is never caught!
throw;
}
}
除方法名外,代码仅做了些微的改变,throw new InvalidOperationException的TaskWrongAsync方法,把返回类型从void改为了Task。按F5运行,点击MainWindow的按钮TaskWrongWithNothing。似乎什么也没有发生,即使DispatcherUnhandledException事件也无法捕获任何异常。在真实的项目中,很可能TaskWrongAsync已经破坏了程序的状态,却没有被任何人察觉。

其实Visual Studio已经嗅出了代码的坏味道,每一个Warning都可能是致命的。在这里我们按照智能提示修复这个Warning,再重新调试看看。
public async void TaskWrongButCatch()
{
try
{
await TaskWrongAsync();
}
catch (Exception)
{
throw;
}
}
通过TaskWrongButCatch方法,我们可以在catch中成功捕获InvalidOperationException。接着在被我们throw后,也可以成功触发DispatcherUnhandledException事件。
接下来对这三种写法的区别做出一些解释,通常async Task方法是将Exception置于Task对象中,在Exception发生时,Task的状态将变成Faulted,然后在执行await操作时,由Task将Exception抛回给调用线程,所以我们可以通过try catch来捕获。
而第一种async void方法,因为返回值没有Task,无法通过await操作将Exception抛回调用线程。async void方法中的Exception将在SynchronizationContext 上抛出,这种情况下无法在async void方法的外部捕捉到Exception。
正确的做法是,避免写async void方法,而是通过Task来返回。只有在作为event处理方法时,才应该编写async void的方法。
第二个例子中我们犯下了更为可怕的错误,Exception被完全掩盖了。第一个例子中虽然我们不能在async void方法外部捕获Exception,但实际Exception对WPF程序而言是可见的,可以通过DispatcherUnhandledException观察到。而有了Task却不await,程序不知道Task何时结束。这个Exception会一直到Task被请求结果时,才会被抛出来。我们可以试试如下代码,异常会在请求Result时被抛出。
static void Main(string[] args)
{
new Program().TaskIntWrongWithResult();
Console.ReadKey();
}
private async Task<int> TaskIntWrongAsync()
{
await Task.Delay();
throw new InvalidOperationException();
}
public void TaskIntWrongWithResult()
{
var result = TaskIntWrongAsync().Result;
Console.WriteLine(result);
}
相对于DispatcherUnhandledException事件,我们确实也可以通过TaskScheduler.UnobservedTaskException事件来检测Task中未被抛出的Exception。但在这里我们能做的仅仅是记录日志,实际绝对不推荐不给Task应用await关键字。
综上所述,async/await异步方法的Exception处理应遵循如下原则:
• 尽量避免async void,而采用async Task方式。
• 应用await给每一个Task返回值。
• 使用async void 作为异步方法链的终结点时,加上try…catch。
• 同理可以推测出对于async lamdba,不要使用Action委托类型,而应该始终使用Func<Task>这样有Task返回的委托类型。
• 通过TaskScheduler.UnobservedTaskException事件来检测漏网之鱼。
本篇所有代码见Github:
https://github.com/manupstairs/AsyncAwaitPractice
.NET Core学习笔记(3)——async/await中的Exception处理的更多相关文章
- .NET异步操作学习之一:Async/Await中异常的处理
以前的异常处理,习惯了过程式的把出现的异常全部捕捉一遍,然后再进行处理.Async/Await关键字出来之后的确简化了异步编程,但也带来了一些问题.接下来自己将对这对关键字进行学习.然后把研究结果放在 ...
- .NET Core学习笔记(7)——Exception最佳实践
1.为什么不要给每个方法都写try catch 为每个方法都编写try catch是错误的做法,理由如下: a.重复嵌套的try catch是无用的,多余的. 这一点非常容易理解,下面的示例代码中,O ...
- .NET CORE学习笔记系列(2)——依赖注入【3】依赖注入模式
原文:https://www.cnblogs.com/artech/p/net-core-di-03.html IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架中以实现对流 ...
- .NET CORE学习笔记系列(2)——依赖注入【2】基于IoC的设计模式
原文:https://www.cnblogs.com/artech/p/net-core-di-02.html 正如我们在<控制反转>提到过的,很多人将IoC理解为一种“面向对象的设计模式 ...
- .NET CORE学习笔记系列(2)——依赖注入【1】控制反转IOC
原文:https://www.cnblogs.com/artech/p/net-core-di-01.html 一.流程控制的反转 IoC的全名Inverse of Control,翻译成中文就是“控 ...
- [译]async/await中阻塞死锁
这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的两篇博文中翻译过来. 原文1:Don'tBlock o ...
- .NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]
原文https://www.cnblogs.com/artech/p/net-core-di-07.html 包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IS ...
- .NET CORE学习笔记系列(2)——依赖注入[6]: .NET Core DI框架[编程体验]
原文https://www.cnblogs.com/artech/p/net-core-di-06.html 毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动 ...
- .NET CORE学习笔记系列(2)——依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...
随机推荐
- Codeforces Round #167 (Div. 1 + Div. 2)
C. Dima and Staircase 线段树维护区间最大值. D. Dima and Two Sequences 由于模数不一定为质数,所以通过拆分质因数来做阶乘取模. E. Dima and ...
- -Bash: Unzip: Command Not Found解决方法 安装unzip
利用unzip命令解压缩的时候,出现-bash: unzip: command not found的错误. unzip——命令没有找到,其原因肯定是没有安装unzip.利用一句命令就可以解决了. 命令 ...
- vue调用兄弟组件的方法使用vueBus调用$emit、$on(只需触发方法即可,不需要考虑传值或参数的问题)
触发方: vueBus.$emit('queryAll') 被触发方: created() { vueBus.$on('queryAll', () => { this.getList() // ...
- Scrap简介
原文:https://blog.csdn.net/ssw_1990/article/details/51254227 提到Python与网络爬虫,可能会想到urllib,urllib2,Beautif ...
- H3C OSPF可选配置命令
- [HNOI2019]白兔之舞
memset0 多合一无聊题 mod k=t,并且k是p-1的约数 单位根反演石锤了. 所以直接设f[i]表示走i步的方案数, 然后C(L,i)分配位置,再A^i进行矩乘得到f[i] 变成生成函数F( ...
- koa2--06.koa-static中间件的使用
koa-static中间件,主要用于设置静态文件资源的文件路径 首先安装koa-static中间,以下是代码示例 const koa = require('koa'); var router = re ...
- 开包即食的教程带你浅尝最新开源的C# Web引擎 Blazor
在今年年初, 恰逢新春佳节临近的时候. 微软给全球的C#开发者们, 着实的送上了一分惊喜. 微软正式开源Blazor ,将.NET带回到浏览器. 这个小惊喜, 迅速的在dotnet开发者中间传开了. ...
- 使用eclipse运行maven web项目 插件/非插件
一.使用插件 tomcat 8.5 tomcat-users.xml中添加这一行就ok <user username="admin" password="admin ...
- 21.time和random
原文:https://www.cnblogs.com/yuanchenqi/article/5732581.html time模块 三种时间表示 在Python中,通常有这几种方式来表示时间: 时间戳 ...