21

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

 2 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU4Mjc4NzgyOQ%3D%3D&%3Bmid=2247484719&%3Bidx=2&%3Bsn=4546cddd6eb39158449d055ede3dab54
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

vAVNbm6.gif

转自:楼上那个蜀黍

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按钮。 非常遗憾程序崩了,并且没有进入断点。

y22aQjB.png!web

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

r2y673e.png!web

其实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

扫码求关注

给我好看

NJfYJfq.jpg!web

您看此文用

·

秒,转发只需1秒呦~

nqIBZv6.png!web

好看你就

点点


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK