在 C# 5.0 In A Nutshell(第 590 页)中,给出了以下示例:
async void ButtonClick (object sender, RoutedEventArgs args)
{
await Task.Delay(1000);
throw new Exception ("Will this be ignored?");
}
该书指出,异常不会被捕获,因为该函数将立即返回到等待行处的消息循环,并且当一秒钟后引发异常时,消息循环中的 try/catch 不会捕获它。
本书继续指出,AsyncVoidMethodBuilder必须将延续封装在另一个函数中,以便它可以构建另一个try/catch块,并将任何捕获的异常转发到同步上下文(如果存在)。
这让我感到困惑,因为我认为,由于 Task.Delay 是从 GUI 线程调用的,同步上下文的存在会导致 Task.Delay 的延续已经在 GUI 线程上执行。 因此,我本以为它可以直接从 try/catch 子句内的消息循环中继续执行,并且仍然会被捕获而无需封装在另一个函数中。
我错过了什么?
您的理解是正确的;异常将直接在 UISynchronizationContext
上重新引发,并被消息循环捕获。从那里,它将传递到 UI 应用程序范围的处理程序。
这本书实际上说的是,async
方法返回后无法捕获异常,这就是为什么AsyncVoidMethodBuilder
会在适当的SynchronizationContext
上重新引发异常的原因。
这很重要,因为async void
方法可能会"离开"其 UI 上下文,例如,通过使用ConfigureAwait(false)
.但是,如果在此点之后发生异常,则必须重新同步到原始SynchronizationContext
,而不一定是引发异常时的上下文。