40

I have seen an example of AggregateException on the web and I'm trying to figure out how it works. I have written a simple example, but my code for some reason doesn't work.

Could someone explain to me what the problem is?

public static void Main()
{
    try
    {
        Parallel.For(0, 500000, i =>
        {
            if (i == 10523)
                throw new TimeoutException("i = 10523");
            Console.WriteLine(i + "\n");
        });
    }
    catch (AggregateException exception)
    {
        foreach (Exception ex in exception.InnerExceptions)
        {
            Console.WriteLine(ex.ToString());
        }
    }
}
Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
Dan
  • 403
  • 1
  • 4
  • 4
  • What is not working? What are you expecting to happen? – Klinger Jul 20 '11 at 00:02
  • Timeout exception was not handled by user code – Dan Jul 20 '11 at 00:04
  • You are saying is that you were expecting the exception to be handled by the catch block but instead you are getting "Timeout exception was not handled by user code", right? If that is the case, Yahia answer and comments is probably what you are looking for. – Klinger Jul 20 '11 at 00:28
  • 5
    A TimeoutException is not an AggregateException, this is why you don't enter the catch region. You have to create manually an AggregateException containing the timeout exception... See my answer below. Cheers. – Sylvain Rodrigue Dec 16 '13 at 15:36

5 Answers5

28

You need to call Handle on the inner exceptions. From MSDN's documentation on Handle:

Each invocation of the predicate returns true or false to indicate whether the Exception was handled. After all invocations, if any exceptions went unhandled, all unhandled exceptions will be put into a new AggregateException which will be thrown. Otherwise, the Handle method simply returns. If any invocations of the predicate throws an exception, it will halt the processing of any more exceptions and immediately propagate the thrown exception as-is.

The example code, also from MSDN:

public static void Main()
{
    var task1 = Task.Run(() => { throw new CustomException("This exception is expected!"); });

    try 
    {
        task1.Wait();
    }
    catch (AggregateException ae)
    {
        // Call the Handle method to handle the custom exception,
        // otherwise rethrow the exception.
        ae.Handle(ex => 
        { 
            if (ex is CustomException)
                Console.WriteLine(ex.Message);
            return ex is CustomException;
        });
    }
}
Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
DLCross
  • 699
  • 5
  • 13
18

AggregateException is often used for catching exceptions, that might occur when waiting for a Task to complete. Because Task in general can consist of multiple others, we do not know, whether there will be one or more exceptions thrown.

Check the following example:

// set up your task
Action<int> job = (int i) =>
{
    if (i % 100 == 0)
        throw new TimeoutException("i = " + i);
};

// we want many tasks to run in paralell
var tasks = new Task[1000];
for (var i = 0; i < 1000; i++)
{
    // assign to other variable,
    // or it will use the same number for every task
    var j = i; 
    // run your task
    var task = Task.Run(() => job(j));
    // save it
    tasks[i] = task;
}

try
{
    // wait for all the tasks to finish in a blocking manner
    Task.WaitAll(tasks);

}
catch (AggregateException e)
{
    // catch whatever was thrown
    foreach (Exception ex in e.InnerExceptions)
        Console.WriteLine(ex.Message);
}
Kędrzu
  • 2,385
  • 13
  • 22
16

When using Parallel the "job" (here counting from 0 to 500000) gets split onto several worker threads. Each of these could throw an exception. In the sample the exception is coded to happen in the thread working on 10523. In a real world scenario more than one exception could happen (in different threads) - the AggregateException is just a "container" for all exceptions occuring while Parallel is running so that you don't lose any exception...

Yahia
  • 69,653
  • 9
  • 115
  • 144
  • 1
    yep, that's fine but what I don't understand is why I get an "error" saying that timeout exception was not handled by user code. Shouldn't aggregate exception catch this exception and print it to console once parallel for completes its work? – Dan Jul 20 '11 at 00:06
  • 2
    you are right but I think the behaviour you describe is just happening when running in VS with debugging on. – Yahia Jul 20 '11 at 00:08
  • @Dan: The exception I get by running your example outside VS and without the catch block is AggregateException: One or more errors occurred. – Klinger Jul 20 '11 at 00:24
  • I don't understand - why are you running without the catch block ? – Yahia Jul 20 '11 at 00:25
  • @Klinger I don't understand what you are trying to say. What should I do to handle this exception? – Dan Jul 20 '11 at 00:31
  • @Yahia: The exception will not change if you run it inside or outside a catch block. I was just describing my little test prog. Obviously if you run it inside a catch block you have the chance to handle the exception by either swalloing it or using the Handle method. – Klinger Jul 20 '11 at 01:03
  • @Dan: See my comment on your question. – Klinger Jul 20 '11 at 01:04
4

My way to resolve it:

var tasks = new Task[] { DoSomethingAsync(), DoSomethingElseAsync() };

try
{
    await Task.WhenAll(tasks).ConfigureAwait(false);
}
catch (AggregateException ex)
{
    var flatAgrExs = ex.Flatten().InnerExceptions;

    foreach(var agrEx in flatAgrExs)
    {
        //handle out errors
        logger.LogError(agrEx, "Critical Error occurred");
    }
}
JPocoata
  • 634
  • 1
  • 11
  • 24
2

Here is a practical use of AggregateException to get the inner exception from an exception object,

    private static Exception GetFirstRealException(Exception exception)
    {
        Exception realException = exception;
        var aggregateException = realException as AggregateException;

        if (aggregateException != null)
        {
            realException = aggregateException.Flatten().InnerException; // take first real exception

            while (realException != null && realException.InnerException != null)
            {
                realException = realException.InnerException;
            }
        }

        return realException ?? exception;
    }
Dhanuka777
  • 8,331
  • 7
  • 70
  • 126