.NET Core 学习 async/await中的Exception处理

栏目: IT技术 · 发布时间: 4年前

内容简介:您看此文用·秒,转发只需1秒呦~

.NET Core 学习 async/await中的Exception处理

转自:楼上那个蜀黍

cnblogs.com/manupstairs/p/12196909.html

前言

写了很多年.NET程序之后,年长的猿类在面对异步编程时,仍不时会犯下致命错误,乃至被拖出去杀了祭天。

本篇就async/await中的Exception处理进行讨论,为种族的繁衍生息做出贡献……

处理async/await中的Exception,最致命的莫过于想抓的Exception抓不到,程序崩的莫名其妙,连日志都没记下来,没法定位错误。 让我们来看以下代码:

private async void SomethingWrongAsync()
{
    await Task.Delay(100);
    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按钮。 非常遗憾程序崩了,并且没有进入断点。

.NET Core 学习 async/await中的Exception处理

这意味着如果我们想在这个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(100);
    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已经破坏了程序的状态,却没有被任何人察觉。

.NET Core 学习 async/await中的Exception处理

其实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(100);
    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 学习 async/await中的Exception处理

您看此文用

·

秒,转发只需1秒呦~

.NET Core 学习 async/await中的Exception处理

好看你就

点点


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

疯狂Java讲义

疯狂Java讲义

李刚 / 电子工业出版社 / 2014-7-1 / 109.00元

《疯狂Java讲义(第3版)(含CD光盘1张)》是《疯狂Java讲义》的第3版,第3版保持了前两版系统、全面、讲解浅显、细致的特性,全面新增介绍了Java 8的新特性,《疯狂Java讲义(第3版)(含CD光盘1张)》大部分示例程序都采用Lambda表达式、流式API进行了改写,因此务必使用Java 8的JDK来编译、运行。 《疯狂Java讲义(第3版)(含CD光盘1张)》深入介绍了Java编......一起来看看 《疯狂Java讲义》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器