令人惊讶的情况,异步方法中的异常处理不会捕获异常

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了令人惊讶的情况,异步方法中的异常处理不会捕获异常相关的知识,希望对你有一定的参考价值。

我的代码将异步方法与异常处理相结合。代码非常简单,等待所有任务,没有async void方法:

async Task DoWorkSafelyWithStateMachine()
{
    try
    {
        await DoWorkThatMightThrowException();
    }
    catch (Exception exception)
    {
        Console.WriteLine("With state machine: " + exception.Message);
    }
}

等待此方法不会抛出异常,因为吞下了异常:

await DoWorkSafelyWithStateMachine(); // No exception thrown

但是,代码并不总是捕获异常,因为它应该这样做。当编译方法以稍微不同的方式编写时,编译器不会创建异步状态机,就会出现问题:

Task DoWorkSafelyWithoutStateMachine()
{
    try
    {
        return DoWorkThatMightThrowException();
    }
    catch (Exception exception)
    {
        Console.WriteLine("Without state machine: " + exception.Message);
        return Task.CompletedTask;
    }
}

该方法没有用async修饰,并且在方法中没有等待。相反,try中方法返回的任务将返回给调用者。但是,根据我的经验,编译器的神奇之处仍然确保如果try中的方法抛出异常,它将被异常处理程序捕获。好吧,显然这并非总是如此。

为了测试同一方法的这两个变体,我让DoWorkThatMightThrowException抛出异常。如果该方法不必在方法体中使用await,则可以使用或不使用异步状态机来实现:

async Task DoWorkThatMightThrowExceptionWithStateMachine()
{
    throw new Exception("With state machine");
    await Task.CompletedTask;
}

Task DoWorkThatMightThrowExceptionWithoutStateMachine()
{
    throw new Exception("Without state machine");
    return Task.CompletedTask;
}

我发现从DoWorkThatMightThrowExceptionWithStateMachine调用DoWorkSafelyWithoutStateMachine并没有发现抛出的异常。其他三种组合确实捕获了异常。特别是,任何一种方法都没有涉及异步状态机的版本捕获异常并且我错误地推断了这一观察结果,不幸的结果是我的一些代码现在有微妙的错误。

                          | Throw + state machine | Throw - state machine |
--------------------------+-----------------------+-----------------------+
Try/catch + state machine |        Caught         |        Caught         |
Try/catch - state machine |      Not caught       |        Caught         |

做这个实验我已经了解到我总是要在await块(表格中的第一行)中执行try任务。但是,我不理解表格第二行的不一致性。请解释一下这种行为。它在哪里记录?搜索有关此信息并不容易。由于不等待任务而导致“丢失”的异常的更基本问题将主导搜索结果。

答案

我不明白表格第二行的不一致性

右侧案例(根本没有国家机器)是微不足道的:

  • 你在try/catch块中调用一个方法
  • 该方法抛出异常
  • 你的catch黑色抓住它 - 没什么特别的

左侧实际上也很容易理解:

  • 你在try/catch块中调用一个方法
  • 但是这个方法看起来并不像它,它已被转换为状态机,所以它返回的是一个Task,表示在实现它时方法内容的执行(1)
  • 所以只要你没有Waitawait返回Task或尝试访问其Result属性,该例外不会(重新)扔进你的try区块内。

(1)我希望我的英语能更好地找到更好更准确的描述。正如Servy指出的那样,在你的例子中,已经出现故障的Task被返回。

另一答案

catch块中抛出异常时,将执行try块。当你编写return DoWorkThatMightThrowExceptionWithStateMachine();时,所有try块正在构建一个标记为出错并返回它的Task。没有异常被抛出,所以没有运行catch块。

如果等待故障任务,它将重新抛出该异常,因此抛出异常。当你调用DoWorkThatMightThrowExceptionWithoutStateMachine时,方法本身会抛出一个异常,而不是返回一个错误的任务,因此在try块中抛出一个异常。

以上是关于令人惊讶的情况,异步方法中的异常处理不会捕获异常的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用Try,Catch捕获异常,程序依然Crash

spring事物回滚机制 (事务异常回滚,捕获异常不抛出就不会回滚)

当对异步任务使用丢弃(_)时,try catch 不会捕获被调用方法的内部异常[重复]

C#多线程开发-处理异步操作中的异常

异常永远不会到达异步方法中的处理程序(C#)[重复]

未捕获异步方法中引发的异常 - 为啥?