Async方法立即抛出异常,但在删除Async关键字时被吞下
本文关键字:Async 关键字 删除 方法 抛出异常 | 更新日期: 2023-09-27 18:15:00
在异步方法中抛出异常时,我无法理解一些行为。
下面的代码将在调用ThrowNow方法时立即抛出异常。如果我注释掉那行并直接抛出异常,那么异常将被吞下,而不会在未观察到的事件处理程序中引发。
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();
}
更令人困惑的是,如果我从ThrowNow
方法中删除async
关键字,异常再次被吞噬。
我认为异步方法同步运行,直到到达阻塞方法。在这种情况下,删除async关键字似乎使它的行为异步。
我认为异步方法在到达阻塞方法之前是同步运行的。
它们是这样做的,但是它们仍然意识到它们是在异步方法中执行的。
如果你直接从async void方法抛出异常,异步机制知道你没有办法观察到异常——它不会被扔回调用者,因为在异步方法中抛出的异常只通过任务传播。(返回的任务出现故障。)在第一个阻塞await
表达式之前直接抛出异常是奇怪的,但是之后的异常处理方式不同。
据我所知,异步void
方法抛出的异常直接传递给同步上下文,如果有的话。(延续被发布到同步上下文中,而同步上下文中只抛出异常。)在一个简单的控制台应用程序中,不是一个同步上下文,所以它被作为一个未报告的异常抛出。
如果你改变你的void
方法返回Task
,那么你只会有一个异常,可以被观察到,但不是(因为你没有使用TestExAsync
的返回值)。
这有意义吗?如果你想要更多的澄清,请告诉我——这有点曲折(我不知道它的文档有多好)。
编辑:我发现了一个位的文档,在c# 5规范章节10.15.2:如果async函数的返回类型为void,则计算与上面的不同之处在于:因为不返回任何任务,所以该函数将完成和异常通知当前线程的同步上下文。同步上下文的确切定义依赖于实现,但它表示当前线程运行的"位置"。当返回空的异步函数的求值开始、成功完成或导致抛出未捕获的异常时,同步上下文将得到通知。
更令人困惑的是,如果我从ThrowNow方法中删除async关键字,异常将再次被吞噬。
异常不会被"吞下"。
除了Jon Skeet所说的,考虑以下代码,其中ThrowNow
没有标记为async
:
static void ThrowNow(Exception ex)
{
throw ex;
}
static async Task TestExAsync()
{
ThrowNow(new System.Exception("Testing"));
await Task.Delay(1000);
}
static void Main()
{
var task = TestExAsync();
Console.WriteLine(task.Exception);
}
正如你所看到的,异常并没有被"吞下",它们只是在异步方法返回的任务中传达给你。
显然,这也意味着您不能try
catch
它们,除非您等待任务:
static void Main()
{
AsyncMain();
}
static async void AsyncMain()
{
var task = TestExAsync();
try
{
await task;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}