我在异步方法中抛出异常时会遇到一些我无法理解的行为。
以下代码将在调用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();
}
更令人困惑的是,如果我从async
方法中移除ThrowNow
关键字,则会再次吞下该异常。
我认为异步方法同步运行,直到达到阻塞方法。在这种情况下,似乎删除async关键字会使其异步运行。
答案 0 :(得分:6)
我认为异步方法同步运行,直到达到阻塞方法。
他们这样做,但他们仍然意识到他们正在异步方法中执行。
如果直接从异步 void 方法抛出异常,异步机制会发现您无法观察到该异常 - 它不会被抛回调用者,因为异步方法中抛出的异常只会通过任务传播。 (返回的任务会出现故障。)在直接抛出第一个阻塞await
表达式之前抛出的异常会很奇怪,但之后的异常会以不同的方式处理。
据我所知,async void
方法引发的异常会直接传递给同步上下文(如果有的话)。 (继续发布到仅抛出异常的同步上下文。)在一个简单的控制台应用程序中,不是同步上下文,因此它被抛出为未报告的异常。
如果您将void
方法更改为返回Task
,那么您只会遇到可以被观察到的异常,但不是(因为您'我没有在TestExAsync
中使用返回值。
这有什么意义吗?如果你想要更多的澄清,请告诉我 - 它有点曲折(我不知道它有多少记录)。
编辑:我在C#5规范第10.15.2节中找到了位文档:
如果async函数的返回类型为void,则评估与以上方式不同:由于没有返回任务,该函数会将当前线程的同步上下文的完成和异常通知。同步上下文的确切定义是依赖于实现的,但是表示当前线程正在运行的“where”。当评估void返回异步函数开始,成功完成或导致未捕获的异常被抛出时,将通知同步上下文。
答案 1 :(得分:0)
更令人困惑的是,如果我从ThrowNow方法中删除了异步关键字,则会再次吞下异常。
例外情况不会被“吞噬”。
除了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);
}
}