如何处理异步方法引发的混乱异常?

时间:2019-07-26 23:41:03

标签: c# .net async-await

异步方法的异常处理有点奇怪。

如果我写一个同步方法,那很清楚。例如,如果我这样做:

public static void Test1()
{
    ThrowException("Test1 has a problem");
}

public static void ThrowException(string message)
{
    throw new MyException(message.ToUpper());
}

我得到一个相对容易阅读的异常:

MyException thrown. Message: TEST1 HAS A PROBLEM
Trace:    at Program.ThrowException(String message)
   at Program.Test1()
   at Program.Main()

但是,如果我做的事情非常相似,但却是异步的:

public static async Task Test2()
{
    await ThrowExceptionAsync("Test2 has a problem");
}

public static async Task ThrowExceptionAsync(string message)
{
    throw new MyException(message.ToUpper());
}

我一团糟:

System.AggregateException thrown. Message: One or more errors occurred.
Trace:    at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Program.Main()
MyException thrown. Message: TEST2 HAS A PROBLEM
Trace:    at Program.<ThrowExceptionAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Program.<Test2>d__1.MoveNext()
MyException thrown. Message: TEST2 HAS A PROBLEM
Trace:    at Program.<ThrowExceptionAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Program.<Test2>d__1.MoveNext()

这带来了两个问题:

  1. 伙计,那东西很难看!
  2. 我无法为catch编写一个MyException块,因为它被包裹在AggregateException中。换句话说,以下操作无效:

    try
    {
        Test2().Wait();
    }
    catch (MyException me)
    {
        //This never fires
    }
    catch(System.AggregateException e)
    {
        //It'll go here instead
    }
    

是否有处理此问题的标准方法?我想让该异常更容易阅读,并且我真的很希望能够捕获MyException而不是捕获AggregateException并对其进行搜索。

Link to DotNetFiddle that demonstrates the issue

2 个答案:

答案 0 :(得分:3)

您有两个选择:

  1. 最佳选择。始终使用await。如果您使用的是ASP.NET,几乎没有理由在整个堆栈中不使用async / await
  2. 如果您绝对必须在异步方法上同步等待(并且不要轻易做出决定),请使用.GetAwaiter().GetResult()。它将获得与.Result相同的值,但也会解开异常。

答案 1 :(得分:3)

  1. 您应该将awaitasynch方法一起使用:它将解压缩异常,使您免于死锁和低效率的代码。
  2. 有时候我使用Ben.Demystifier来清理痕迹,它可能也对您的情况有用。

死锁脱离主题:async方法捕获当前的同步上下文,因此,如果尝试在同一同步上下文中等待完成,则会出现死锁。

public partial class Form1 : Form
{
   public Form1() => InitializeComponent();

   private void button1_Click(object sender, EventArgs e)
          => doSomthing().Wait();

   private async Task doSomthing() =>  await Task.Delay(1000);
}

要不捕获上下文,可以使用ConfigureAwait

   private void button1_Click(object sender, EventArgs e)
          => doSomthing()
             .ConfigureAwait(continueOnCapturedContext: false)
             .GetAwaiter()
             .GetResult();

   private async Task doSomthing() =>  await Task.Delay(1000);

This article深入讨论了此主题,如果您对asynch / await的陷阱感兴趣,请随时阅读。