来自article on async await by Stephen Cleary:
图2无法捕获异步空洞方法的异常
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught here!
throw;
}
}
...异步void方法抛出的任何异常将直接在异步void方法启动时处于活动状态的SynchronizationContext上引发...
这究竟意味着什么?我写了一个扩展示例来尝试收集更多信息。它具有与图2相同的行为:
static void Main()
{
AppDomain.CurrentDomain.UnhandledException += (sender, ex) =>
{
LogCurrentSynchronizationContext("AppDomain.CurrentDomain.UnhandledException");
LogException("AppDomain.CurrentDomain.UnhandledException", ex.ExceptionObject as Exception);
};
try
{
try
{
void ThrowExceptionVoid() => throw new Exception("ThrowExceptionVoid");
ThrowExceptionVoid();
}
catch (Exception ex)
{
LogException("AsyncMain - Catch - ThrowExceptionVoid", ex);
}
try
{
// CS1998 C# This async method lacks 'await' operators and will run synchronously.
async void ThrowExceptionAsyncVoid() => throw new Exception("ThrowExceptionAsyncVoid");
ThrowExceptionAsyncVoid();
}
// exception cannot be caught, despite the code running synchronously.
catch (Exception ex)
{
LogException("AsyncMain - Catch - ThrowExceptionAsyncVoid", ex);
}
}
catch (Exception ex)
{
LogException("Main", ex);
}
Console.ReadKey();
}
private static void LogCurrentSynchronizationContext(string prefix)
=> Debug.WriteLine($"{prefix} - " +
$"CurrentSynchronizationContext: {SynchronizationContext.Current?.GetType().Name} " +
$"- {SynchronizationContext.Current?.GetHashCode()}");
private static void LogException(string prefix, Exception ex)
=> Debug.WriteLine($"{prefix} - Exception - {ex.Message}");
调试输出:
Exception thrown: 'System.Exception' in ConsoleApp3.dll
AsyncMain - Catch - ThrowExceptionVoid - Exception - ThrowExceptionVoid
Exception thrown: 'System.Exception' in ConsoleApp3.dll
An exception of type 'System.Exception' occurred in ConsoleApp3.dll but was not handled in user code
ThrowExceptionAsyncVoid
AppDomain.CurrentDomain.UnhandledException - CurrentSynchronizationContext: -
AppDomain.CurrentDomain.UnhandledException - Exception - ThrowExceptionAsyncVoid
The thread 0x1c70 has exited with code 0 (0x0).
An unhandled exception of type 'System.Exception' occurred in System.Private.CoreLib.ni.dll
ThrowExceptionAsyncVoid
The program '[18584] dotnet.exe' has exited with code 0 (0x0).
我想了解更多详情
async void
(没有await
)和void
之间有什么区别?
CS1998 C# This async method lacks 'await' operators and will run synchronously.
await
同步运行,为什么它与简单void
的行为不同?async Task
没有await
的行为与Task
的行为有何不同?async void
和async Task
之间的编译器行为有何不同?是Task
对象真的是根据建议here为async void
创建的吗?编辑。 要明确,这不是关于最佳做法的问题 - 这是关于编译器/运行时实现的问题。
答案 0 :(得分:6)
如果没有当前的同步上下文(如我的示例所示),引发的异常在哪里?
By convention,当SynchronizationContext.Current
为null
时,SynchronizationContext.Current
与new SynchronizationContext()
的实例相同,等同于async
的实例。换句话说,"没有同步上下文"与"线程池同步上下文"。
因此,您所看到的行为是catch
状态机正在捕获异常,然后直接在线程池线程上提升它,在线程池线程中无法捕获async void
。
这种行为看起来很奇怪,但请考虑这种方式:async void
适用于事件处理程序。所以考虑一个提升事件的UI应用程序;如果它是同步的,那么任何异常都会传播到UI消息处理循环。 await
行为旨在模仿:在UI消息处理循环中重新引发任何异常(包括System.Threading.Timer
之后的异常)。同样的逻辑应用于线程池上下文;例如,同步System.Threading.Timer
回调处理程序中的异常将直接在线程池上引发,异步async
回调处理程序中的异常也会引发。
如果它同步运行而没有等待,为什么它的行为与简单的无效?
async Task
状态机正在专门处理异常。
没有等待的异步任务的行为与任务有什么不同吗?
绝对。 Task
有一个非常相似的状态机 - 它捕获代码中的任何异常并将它们放在返回的async Task
上。这是one of the pitfalls in eliding async
/await
for non-trivial code。
async void和async Task之间的编译器行为有何不同。
对于编译器,区别在于如何处理异常。
正确的思考方式是async void
是一种自然而恰当的语言发展; async
是一个奇怪的黑客,C#/ VB团队采用它来启用异步事件而没有巨大的向后兼容性问题。其他await
/ async void
启用的语言(如F#,Python和JavaScript)没有brew cask install
的概念......从而避免了所有陷阱。
是否真的为async void创建了一个Task对象,如此处所示?
没有
答案 1 :(得分:-1)