`async void`(没有等待)与 `void` 之间有什么区别
What are the differences between `async void` (with no await) vs `void`
取自 article Stephen Cleary 的异步等待:
图 2 无法使用 Catch 捕获 Async Void 方法的异常
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught here!
throw;
}
}
... any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started...
这到底是什么意思?我写了一个扩展示例来尝试收集更多信息。它具有与图 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
不同?
- 没有
await
的 async Task
与 Task
的行为是否也不同?
async void
和 async Task
之间的编译器行为有何不同。 Task
对象真的是按照建议 here 为 async void
创建的吗?
编辑。 明确地说,这不是关于最佳实践的问题 - 这是关于编译器/运行时实现的问题。
- async void - 它不能被等待,它允许你触发或忘记方法
- 异步任务 - 可以等待,但 return 没有任何值
- 异步任务方法名{ return默认(T); } - 可以等待,returns 是 T
类型的值
- void - 没有参数将被 returned
If there is no current synchronization context (as in my example), where is the exception raised?
By convention,当SynchronizationContext.Current
是null
时,这实际上与SynchronizationContext.Current
等于new SynchronizationContext()
的一个实例是一样的。换句话说,"no synchronization context" 与 "thread pool synchronization context".
相同
因此,您看到的行为是 async
状态机正在捕获异常,然后直接在线程池线程上引发它,而 catch
无法捕获异常。
这种行为看起来很奇怪,但请这样想:async void
是为事件处理程序设计的。所以考虑一个 UI 应用程序引发一个事件;如果它是同步的,那么任何异常都会传播到 UI 消息处理循环。 async void
行为旨在模仿:在 UI 消息处理循环中重新引发任何异常(包括 await
之后的异常)。同样的逻辑也适用于线程池上下文;例如,同步 System.Threading.Timer
回调处理程序的异常将直接在线程池上引发,异步 System.Threading.Timer
回调处理程序的异常也是如此。
If it runs synchronously with no await, why is it behaving differently from simply void?
async
状态机正在专门处理异常。
Does async Task with no await also behave differently from Task?
当然可以。 async Task
有一个非常相似的状态机——它从您的代码中捕获任何异常并将它们放在返回的 Task
上。这是 one of the pitfalls in eliding async
/await
for non-trivial code.
What are the differences in compiler behaviour between async void and async Task.
对于编译器而言,不同之处在于如何处理异常。
正确的思考方式是async Task
是一种自然而恰当的语言发展; async void
是 C#/VB 团队采用的一种奇怪的 hack 来启用异步事件而没有巨大的向后兼容性问题。其他启用 async
/await
的语言,如 F#、Python 和 JavaScript 没有 async void
的概念...因此避免了所有的陷阱.
Is a Task object really created under-the-hood for async void as suggested here?
没有
取自 article Stephen Cleary 的异步等待:
图 2 无法使用 Catch 捕获 Async Void 方法的异常
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught here!
throw;
}
}
... any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started...
这到底是什么意思?我写了一个扩展示例来尝试收集更多信息。它具有与图 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
不同? - 没有
await
的async Task
与Task
的行为是否也不同?
- 编译器警告
async void
和async Task
之间的编译器行为有何不同。Task
对象真的是按照建议 here 为async void
创建的吗?
编辑。 明确地说,这不是关于最佳实践的问题 - 这是关于编译器/运行时实现的问题。
- async void - 它不能被等待,它允许你触发或忘记方法
- 异步任务 - 可以等待,但 return 没有任何值
- 异步任务方法名{ return默认(T); } - 可以等待,returns 是 T 类型的值
- void - 没有参数将被 returned
If there is no current synchronization context (as in my example), where is the exception raised?
By convention,当SynchronizationContext.Current
是null
时,这实际上与SynchronizationContext.Current
等于new SynchronizationContext()
的一个实例是一样的。换句话说,"no synchronization context" 与 "thread pool synchronization context".
因此,您看到的行为是 async
状态机正在捕获异常,然后直接在线程池线程上引发它,而 catch
无法捕获异常。
这种行为看起来很奇怪,但请这样想:async void
是为事件处理程序设计的。所以考虑一个 UI 应用程序引发一个事件;如果它是同步的,那么任何异常都会传播到 UI 消息处理循环。 async void
行为旨在模仿:在 UI 消息处理循环中重新引发任何异常(包括 await
之后的异常)。同样的逻辑也适用于线程池上下文;例如,同步 System.Threading.Timer
回调处理程序的异常将直接在线程池上引发,异步 System.Threading.Timer
回调处理程序的异常也是如此。
If it runs synchronously with no await, why is it behaving differently from simply void?
async
状态机正在专门处理异常。
Does async Task with no await also behave differently from Task?
当然可以。 async Task
有一个非常相似的状态机——它从您的代码中捕获任何异常并将它们放在返回的 Task
上。这是 one of the pitfalls in eliding async
/await
for non-trivial code.
What are the differences in compiler behaviour between async void and async Task.
对于编译器而言,不同之处在于如何处理异常。
正确的思考方式是async Task
是一种自然而恰当的语言发展; async void
是 C#/VB 团队采用的一种奇怪的 hack 来启用异步事件而没有巨大的向后兼容性问题。其他启用 async
/await
的语言,如 F#、Python 和 JavaScript 没有 async void
的概念...因此避免了所有的陷阱.
Is a Task object really created under-the-hood for async void as suggested here?
没有