2

Task.WaitAll method can throw an AggregateException, but Task.WaitAny method does not.

Questions:

  1. I do not understand for what purpose the developers of the framework did this?
  2. How to catch an exception from a task using Task.WaitAny method?

Example:

using System;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            void MyMethodForDivideByZeroException()
            {
                int a = 1;
                a = a / 0;
            }

            void MyMethodForIndexOutOfRangeException()
            {
                int[] MyArrayForInt = new int[2] { 1, 2 };
                Console.WriteLine(MyArrayForInt[2]);
            }

            Task MyTask22 = new Task(MyMethodForDivideByZeroException);
            Task MyTask23 = new Task(MyMethodForIndexOutOfRangeException);

            MyTask22.Start();
            MyTask23.Start();

            Task.WaitAny(MyTask22, MyTask23); //No exceptions
            //Task.WaitAll(MyTask22, MyTask23); //AggregateException

            Console.WriteLine(MyTask22.Status);
            Console.WriteLine(MyTask23.Status);
        }
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    As a side note: prefer `WhenAny`/`WhenAll` over `WaitAny`/`WaitAll` as the former are also async while the latter are sync. – ckuri Feb 02 '22 at 08:17

2 Answers2

2

WaitAny() waits for any one of the tasks to complete (successfuly or otherwise).
When one does, with an exception, the exception is not propagated (thrown).

WaitAll() waits for all of the tasks to complete.
As they both throw an exception, these are aggregated into an AggregateException.

You can check for errors like this:

var tasks = new[] { MyTask22, MyTask23 };
int taskIndex = Task.WaitAny(tasks); //No exceptions
        
if (taskIndex >= 0)
{
    throw tasks[taskIndex].Exception;
}

More useful information here:

g t
  • 7,287
  • 7
  • 50
  • 85
0

The job of the Task.WaitAny method is to inform you that a task has completed, not that a task has completed successfully.

I'll answer your questions in reverse order:

  1. How to catch an exception from a task using Task.WaitAny method?

You probably ask how to throw the exception of a completed task that has failed. The simplest way is probably just to Wait the task:

var tasks = new Task[] { MyTask22, MyTask23 };
int completedTaskIndex = Task.WaitAny(tasks);
tasks[completedTaskIndex].Wait();
  1. I do not understand for what purpose the developers of the framework did this?

Because otherwise the method would not be able to do its intended job. You won't be able to use this method in order to be informed about the completion of a task. Instead of getting the index of the completed task, you would get a useless and expensive exception. You could catch this exception, but you would still don't know which of the tasks is the one that caused the exception.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • The API design is inconsistent and confusing. If the job of WaitAny is it tell you 'when any task is finished' (and which one), then the job of WaitAll should parallel it, telling you only when all tasks have completed. The fact that one 'WaitAll' throws exceptions and 'WaitAll' does not throw was a poor design choice. Especially considering that Task.Wait() (i.e. waiting on a single take to complete) throws exception. Meanwhile, 'WaitAny' (also waiting on a single task to complete) does not throw exceptions. – Triynko Mar 21 '22 at 01:03
  • A more consistent design would be if Wait, WaitAny, WaitAll, WhenAny, and WhenAll all threw the same type of wrapper exception like AggregateTaskException, such that it includes just a list of Tasks that ended with exceptions. The Tasks already embed their own Exception when in the errored state, and again, instead of that Exception property being of type AggregateException (divorced from the owning Task(s)), it should be of type AggregateTaskException that includes a list of Tasks with exceptions. – Triynko Mar 21 '22 at 01:08
  • @Triynko why not do the opposite, and make sure that none of the `Wait` family of APIs (`Wait`, `WaitAny`, `WaitAll`) throws exception? This way we would `Wait` safely the completion of a task, and if we wanted the propagation of the stored exception we would call an API like: `ThrowIfFaultedOrCanceled`. That's what Microsoft should have done back in 2010. Definitely. Now we just need to invent a time machine, and go back in time and correct their mistake! – Theodor Zoulias Mar 21 '22 at 04:58