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

Mar*_*age 2 .net c# exception-handling try-catch async-await

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

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

等待这个方法不会抛出异常,因为异常被吞了:

await DoWorkSafelyWithStateMachine(); // No exception thrown
Run Code Online (Sandbox Code Playgroud)

但是,代码并不总是像预期的那样捕获异常。当该方法以稍微不同的方式编写时,编译器没有创建异步状态机,就会出现问题:

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

The method is not decorated with async and nothing is awaited inside the method. Instead the task returned by the method inside try is returned to the caller. However, in my experience the magic of the compiler somehow still ensures that if the method inside try throws an exception it gets caught by the exception handler. Well, apparently that is not always true.

To test these two variations of the same method I let DoWorkThatMightThrowException throw an exception. If the method does not have to use await in the body of the method then it can be implemented either with or without an async state machine:

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

Task DoWorkThatMightThrowExceptionWithoutStateMachine()
{
    throw new Exception("Without state machine");
    return Task.CompletedTask;
}
Run Code Online (Sandbox Code Playgroud)

I have discovered that calling DoWorkThatMightThrowExceptionWithStateMachine from DoWorkSafelyWithoutStateMachine does not catch the exception thrown. The other three combinations do catch the exception. In particular, the version where no async state machines are involved in either method catches the exception and I have mistakenly extrapolated this observation with the unfortunate result that some of my code now has subtle errors.

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

在做这个实验时,我了解到我总是需要await在一个try块内完成一项任务(表中的第一行)。但是,我不明白表格第二行的不一致之处。请解释这种行为。它在哪里记录?搜索这方面的信息并不容易。由于没有等待任务而导致异常“丢失”的更基本问题将支配搜索结果。

Ser*_*rvy 5

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

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