John Wu
John Wu

Reputation: 52290

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

Upvotes: 0

Views: 1145

Answers (2)

fenixil
fenixil

Reputation: 2124

  1. You should use await with async 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. 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);
}

You can use ConfigureAwait to skip capturing the context:

   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 async/await.

Upvotes: 2

Gabriel Luci
Gabriel Luci

Reputation: 40988

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.

Upvotes: 2

Related Questions