为什么我无法在具有void返回类型的异步函数中捕获异常?

c#

static void Main(string[] args) {
   try {
      var a = MyMethodAsync();
      a.Wait();  // calling Wait throw an AggregateException 
   }
   catch (Exception e) {
      Console.WriteLine("Catch");
   }

   Console.ReadLine();
}

static async Task<String> MyMethodAsync() {    
   String s = await TestThrowException();    
   return s;
}

static Task<String> TestThrowException() {
   return Task.Run(() => {
      throw new DivideByZeroException();
      return "placeholder";   // return statement is needed for the compilier to work correctly
   });
}

上面的代码有效, Main 方法中的 catch 块可以捕获AggregateException异常(源自TestThrowException并转换为AggregateException)。

但如果我有这样的代码:

static void Main(string[] args) {
   try {
      MyMethodAsync(); 
   }
   catch (Exception e) {
      Console.WriteLine("Catch");
   }
   Console.ReadLine();
}

static async void MyMethodAsync() {
   await TestThrowException();
}

static Task<String> TestThrowException() {
   return Task.Run(() => {
      throw new DivideByZeroException();
      return "placeholder";   
}

那么 Main 方法中的 catch 块无法捕获任何异常,这是为什么呢?

回答

任何时候async void,您基本上都会破坏正确发出完成失败信号的能力;它可以报告失败的唯一方法是异常是否立即await发生并且在任何不完整之前发生——即同步发生。在您的情况下,Task.Run保证这不是同步的,因此任何关于结果和失败的知识都丢失了。

从根本上说,永远不要写async void(除非你绝对必须,例如在事件处理程序中)。除了上述,它的问题已公知一些并发症SynchronizationContext实现(尤其是遗留ASP.NET一个),该装置简单地调用的async void方法是足够的崩溃你的应用程序(至少假设;同步上下文警告应用于更多库作者而不是应用程序作者,因为库作者无法选择应用程序执行环境)。

删除async void. 如果你想返回“nothing”,那么你应该使用async Taskasync ValueTask作为签名:

static async Task MyMethodAsync() {
   await TestThrowException();
}

(也许也可以简化为)

static Task MyMethodAsync()
    => TestThrowException();

和:

static async Task Main(string[] args) {
   try {
      await MyMethodAsync(); 
   }
   catch (Exception e) {
      Console.WriteLine("Catch");
   }
   Console.ReadLine();
}


以上是为什么我无法在具有void返回类型的异步函数中捕获异常?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>