`async void`(没有等待)vs`void`之间有什么区别?

时间:2017-07-22 09:13:01

标签: c# .net async-await

来自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 voidasync Task之间的编译器行为有何不同?是Task对象真的是根据建议hereasync void创建的吗?

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

2 个答案:

答案 0 :(得分:6)

  

如果没有当前的同步上下文(如我的示例所示),引发的异常在哪里?

By convention,当SynchronizationContext.Currentnull时,SynchronizationContext.Currentnew 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)

  1. async void - 无法等待它,它允许你触发或忘记方法
  2. async Task - 可以等待,但不返回任何值
  3. async Task methodName {return default(T); - 可以等待它,并返回T
  4. 类型的值
  5. void - 不会返回任何参数