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

Posted

技术标签:

【中文标题】为啥我无法在具有 void 返回类型的异步函数中捕获异常?【英文标题】:why I couldn't catch the exception in async function that has void return type?为什么我无法在具有 void 返回类型的异步函数中捕获异常? 【发布时间】:2021-05-26 12:28:40 【问题描述】:
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块无法捕捉到任何异常,这是为什么呢?

【问题讨论】:

调用MyMethodAsync(); 只是启动任务,而不是等待它。因此,当引发异常时,您的程序已经离开了 try...catch。这就是为什么您通常应该避免使用async void 因为 Wait() 调用。见这里:***.com/questions/5383310/… @Bobbey Wait 不是这里的关键 @KlausGütter 感谢您的回答。但是即使我把Thread.Sleep(1000);放在MyMethodAsync(); 下面让程序停留在try catch,它仍然无法捕获任何异常。 其实不是时间问题,而是沟通问题。如果没有 Task 对象,您的任务就无法与调用者交流发生了异常。 【参考方案1】:

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

从根本上说,永远不要写async void(除非你绝对必须这样做,例如在事件处理程序中)。除了上述问题之外,已知一些 SynchronizationContext 实现的复杂性(特别是旧版 ASP.NET 实现),这意味着 只需调用 async void 方法就足够了使您的应用程序崩溃(至少在假设情况下;同步上下文警告更适用于库作者而不是应用程序作者,因为库作者无法选择应用程序执行环境)。

删除async void。如果要返回“无”,则应使用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();

【讨论】:

“如果异常发生......同步” - 这是早期 CTP 的行为。它在后来的 CTP 中进行了更改。现在async 方法中的所有异常都会得到一致的处理,无论它们是在await 之前还是之后引发的。 @StephenCleary 是的,我更倾向于从调用者的角度而不是被调用者的角度来考虑它 - 而调用者方面,他们不知道被调用者的实现细节,等待的被调用者可以抛出以“旧”的方式微不足道(主要是:不是async) - 但是是的:如果我们将自己限制在async 方法,你是对的(假设我们不做任何像自定义异步构建器这样的异国情调,在这种情况下,无论如何,所有赌注都取消了!)。无论如何:“不要使用async void”作为解决方法:)

以上是关于为啥我无法在具有 void 返回类型的异步函数中捕获异常?的主要内容,如果未能解决你的问题,请参考以下文章

当一个函数无返回值时,函数的类型应定义为啥

使用异步而不等待

无法修复异步的返回类型

为啥我们需要 void 函数? [关闭]

从函数中的 void * 参数返回数组

具有 UNNotificationRequest 的计算属性返回无法将类型“Void”的返回表达式转换为返回类型“[UNNotificationRequest]”