Why I couldn't catch the exception in async function that has void return type?

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

The code above works, the catch block in Main method can catch the AggregateException exception (originate from TestThrowException and get converted into AggregateException).

But if I have the code like this:

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

then the catch block in Main method cannot catch any exception, why is that?

1 Answers

Any time you have async void, you're basically breaking the ability to correctly signal completion and failure; the only way it can report failure is if the exception happens immediately and before any incomplete await - i.e. synchronously. In your case, the Task.Run guarantees that this is not synchronous, hence any knowledge of the outcome and failure: is lost.

Fundamentally, never write async void (unless you absolutely have to, for example in an event-handler). In addition to the problem above, it also has known complications with some SynchronizationContext implementations (in particular the legacy ASP.NET one), which means simply invoking an async void method is enough to crash your application (at least hypothetically; the sync-context caveat applies more to library authors than application authors, since library authors don't get to choose the application execution environment).

Remove the async void. If you want to return "nothing", then you should use async Task or async ValueTask as the signature:

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

(which could perhaps also be simplified to)

static Task MyMethodAsync()
    => TestThrowException();

and:

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