异步方法立即抛出异常但在删除异步关键字时被吞下

Vin*_*cio 5 c# asynchronous async-await

我在异步方法中抛出异常时遇到了一些我无法理解的行为.

以下代码将在调用ThrowNow方法时立即抛出异常.如果我将该行注释掉并直接抛出异常,那么吞没异常并且不会在Unobserved事件处理程序中引发异常.

public static async void ThrowNow(Exception ex){
    throw ex;
}

public static async Task TestExAsync()
{
    ThrowNow(new System.Exception("Testing")); // Throws exception immediately
    //throw new System.Exception("Testing");   // Exception is swallowed, not raised in unobserved event

    await Task.Delay(1000);
}

void Main()
{  
    var task = TestExAsync();
}
Run Code Online (Sandbox Code Playgroud)

更令人困惑的是,如果我asyncThrowNow方法中删除关键字,则会再次吞下异常.

我认为异步方法同步运行,直到达到阻塞方法.在这种情况下,似乎删除async关键字使其行为异步.

Jon*_*eet 7

我认为异步方法同步运行,直到达到阻塞方法.

他们这样做,但他们仍然意识到他们正在异步方法中执行.

如果直接从异步void方法抛出异常,异步机制会意识到您无法观察到该异常 - 它不会被抛回调用者,因为异步方法中抛出的异常只会通过任务.(返回的任务会出现故障.)第一个阻塞await表达式直接抛出之前抛出异常会很奇怪,但之后的异常会以不同的方式处理.

据我所知,异步void方法抛出的异常会直接传递给同步上下文(如果有的话).(继续发布到仅抛出异常的同步上下文.)在一个简单的控制台应用程序中,没有同步上下文,因此它被抛出为未报告的异常.

如果你改变你的void方法返回Task,那么你只会有一个可以观察到的异常,但不是(因为你没有使用返回值TestExAsync).

这有任何意义吗?如果你想要更多的澄清,请告诉我 - 它有点曲折(我不知道它有多少记录).

编辑:我在C#5规范部分10.15.2中找到了一些文档:

如果async函数的返回类型为void,则评估与以上方式的不同之处在于:由于未返回任何任务,因此该函数会将当前线程的同步上下文的完成和异常通知.同步上下文的确切定义是依赖于实现的,但是表示当前线程正在运行的"where".当评估void返回异步函数开始,成功完成或引发未捕获的异常时,将通知同步上下文.