skip to Main Content

I’m trying to understand async/await in .net8, especially exception handling and I’m running into some confusion, especially regarding the documentation as while it specifies some of the approach it doesn’t give enough examples for me to understand what’s happening.

I’m using the following trivial code just to watch something work:

private static async Task Main(string[] args) {
 var sleeps = new int[] { 1, 2, 3, 0 };
 var tasks = new List<Task<int>>();
 foreach (var t in sleeps)
 {
     Console.WriteLine("Submitting task: {0}", t);
     tasks.Add(sleep(t));
 }
 Console.WriteLine("All tasks submitted, waiting for all to complete");
 try
 {
     Task.WaitAll(tasks.ToArray());
 } catch (DivideByZeroException e)
 {
     Console.WriteLine("One of the tasks failed: {0}", e);
 }
 Console.WriteLine("All tasks completed, collecting results");
 foreach (var t in tasks)
 {
     Console.WriteLine("Task returned: {0}", t.Result);
 }
}

 private static async Task<int> sleep(int s)
 {
     Console.WriteLine("Sleeping for: [ {0} ]", s);
     if (s == 0)
     {
         // this is just to throw something to be caught
         throw new DivideByZeroException("0 can't be delayed");
     }
     await Task.Delay(s * 1000);
     Console.WriteLine("tSlept for: [ {0} ]", s);
     return s;
 }

I’m following documentation from here:

and here:

According the docs I should try to throw exceptions in my async functions BEFORE entering the async portion of the code. Which you can see in my example I am, I am throwing a ZeroDivision exception before awaiting the Task.Delay.

When I run this code in visual studio, I would expect to hit my DivideByZeroException catch block, but instead I get a SystemAggregateException, which I understand is how .net collects all of the async exceptions, however the documentation explicitly states: When code awaits a faulted task, the first exception in the AggregateException.InnerExceptions collection is rethrown. However, I am not getting the DivideByZero exception thrown at all, instead I am getting 2 SystemAggregate exceptions throw sequentially each with their first innerException being the DivideByZero exception.

What is going on here.

I have also coded the breakfast example here: Asynchronous programming with async and await and added the toaster on fire there is even more confusing, in that I DO NOT get an exception throw anywhere when I explicitly throw one. I must inspect the Task.Exception to see if it was thrown.

So can anyone explain what is happening here?

What is the approach I would use if I wanted to submit 5 async tasks, wait for all of them to complete and then iterate over them to inspect either their Result or Exception properties? I am not understanding when to try/catch, when to inspect Task.Exception and why I’m getting SystemAggregate exceptions in my example code.

2

Answers


  1. The AggregateException is not thrown by your asynchronous sleep method. It is thrown by the synchronous Task.WaitAll method:

    Exceptions

    AggregateException

    At least one of the Task instances was canceled. If a task was canceled, the AggregateException contains an OperationCanceledException in its InnerExceptions collection.

    -or-

    An exception was thrown during the execution of at least one of the Task instances.

    The best solution to your problem is most likely to replace the Task.WaitAll with the asynchronous Task.WhenAll:

    try
    {
        await Task.WhenAll(tasks.ToArray());
    }
    catch (DivideByZeroException e)
    {
        Console.WriteLine("One of the tasks failed: {0}", e);
    }
    

    This will also eliminate a warning that you are probably seeing regarding the async Task Main method not containing any await.

    In case you want to wait synchronously the tasks, and you like the behavior of await of propagating just one exception, you can use the .GetAwaiter().GetResult() like this:

    try
    {
       Task.WhenAll(tasks.ToArray()).GetAwaiter().GetResult();
    }
    catch (DivideByZeroException e)
    {
        Console.WriteLine("One of the tasks failed: {0}", e);
    }
    

    This will block the current thread until all tasks complete, and will propagate the exception of one of the failed tasks, either positionally or chronologically, depending on the .NET runtime version. You can look at this GitHub issue for details about which task’s exception is propagated.

    Login or Signup to reply.
  2. Your exception is getting thrown by the sync portion of your sleep function, which means that the point which exception catching/unwinding takes place is here:

    foreach (var t in sleeps)
     {
         Console.WriteLine("Submitting task: {0}", t);
         tasks.Add(sleep(t));  // <-- here, when t == 0
     }
    

    It has nothing to do with tasks at all, this is strictly a synchronous exception thrown in a synchronous function (Main, before the first async point). If you want to catch it, that’s where your try..catch should be.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search