简体   繁体   中英

How do I handle the messy exception that is thrown by an async method?

Exception handling for asynchronous methods is a little weird.

If I write a synchronous method, it's pretty clear. For example, if I do this:

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

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

I get an exception that is relatively easy to read:

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

But if I do something very similar, but async:

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

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

I get this mess:

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()

This presents two problems:

  1. Man, that stuff is hard to read!
  2. I can't write a catch block for MyException because it gets wrapped in an AggregateException. In other words, the following does not work:

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

Is there a standard way of dealing with this? I'd sort of would like the exception to be easier to read, and I really would like to be able to catch MyException instead of catching AggregateException and searching for it.

Link to DotNetFiddle that demonstrates the issue

You have two options:

  1. The best option. Always use await . If you're using ASP.NET, there is almost never a reason to not use async / await through the whole stack.
  2. If you absolutely must wait synchronously on an asynchronous method (and don't make that decision lightly), then use .GetAwaiter().GetResult() . It will get the same value as .Result , but it will also unwrap an exception.
  1. You should use await with asynch methods: it will unpack the exception, save you from the deadlock and inefficient code.
  2. Sometime ago I used Ben.Demystifier to cleanup traces, it might be useful for your case too.

Deadlock off-topic: async method captures current synchronization context so if you'll try to wait for the completion in the same synchronization context, you'll get a deadlock.

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);
}

To do not capture the context you can use 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 covers this topic in depth, feel free to read it if you are interested in the pitfalls of asynch / await .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM