gpt4 book ai didi

c# - `async void`(没有等待)与 `void` 之间有什么区别

转载 作者:太空狗 更新时间:2023-10-29 23:12:40 26 4
gpt4 key购买 nike

取自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 不同?
    • 没有 awaitasync Task 的行为是否也与 Task 不同?
  • async voidasync Task 之间的编译器行为有何不同。 Task 对象是否真的按照建议在后台为 async void 创建 here

编辑。需要说明的是,这不是关于最佳实践的问题 - 这是关于编译器/运行时实现的问题。

最佳答案

If there is no current synchronization context (as in my example), where is the exception raised?

By convention ,当 SynchronizationContext.Currentnull 时,这实际上与 SynchronizationContext.Current 相同,等于 new SynchronizationContext()< 的实例。换句话说,“无同步上下文”与“线程池同步上下文”是一样的。

因此,您看到的行为是 async 状态机正在捕获异常,然后直接在线程池线程上引发它,而 catch.

这种行为看起来很奇怪,但可以这样想:async void 是为事件处理程序设计的。所以考虑一个引发事件的 UI 应用程序;如果它是同步的,那么任何异常都会传播到 UI 消息处理循环。 async void 行为旨在模拟:任何异常(包括 await 之后的异常)都会在 UI 消息处理循环中重新引发。同样的逻辑也适用于线程池上下文;例如,同步 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?

没有。

关于c# - `async void`(没有等待)与 `void` 之间有什么区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45252771/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com