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

时间:2017-12-19 16:54:24

标签: c# .net exception-handling async-await try-catch

我的代码将异步方法与异常处理相结合。代码非常简单,所有任务都在等待,并且没有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执行任务(表中的第一行)。但是,我不理解表格第二行的不一致性。请解释一下这种行为。它在哪里记录?搜索有关此信息并不容易。异常被丢失的更基本问题"因为不等待任务将主导搜索结果。

2 个答案:

答案 0 :(得分:3)

  

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

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

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

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

  • 您在try/catch区块内调用方法
  • 但是这种方法看起来并不像它,它已被转换为一种状态 机器,所以它返回的是Task,表示在实现方法时执行方法的内容(1)
  • 因此,只要您没有Waitawait返回Task或尝试访问其Result属性,就不会(重新)抛出异常在try区块内。

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

答案 1 :(得分:3)

catch块中抛出异常时,try块将执行。当您编写return DoWorkThatMightThrowExceptionWithStateMachine();时,所有try块正在构建标记为出错的Task并返回它。不会抛出任何异常,因此不会运行catch块。

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