async void、await 和 exception - 为什么在 GUI 线程的"await"之后引发的异常需要 AsyncVoidMethodBuilder 进行封送处理?



在 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,而不一定是引发异常时的上下文。

最新更新