BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Parallel and Asynchronous
.NET 4.0+

AggregateException.WithoutCancellations Method

Many parallel operations throw AggregateExceptions when problems occur. These may contain a mixture of real problems and exceptions thrown during cancellation. The WithoutCancellations extension method makes it easy to ignore the cancellation exceptions.

Cancellation and AggregateExceptions

When you create parallel tasks, parallel loops or Parallel Language-Integrated Query (PLINQ) queries, you will find that you have to catch AggregateExceptions. These are containers for a number of inner exceptions and are required because it is possible for parallel operations to generate multiple exceptions, all of which may require recovery steps to be taken.

The cancellation token mechanism used by the Task Parallel Library to cancel parallel operations also makes use of exceptions, in the form of the OperationCanceledException and its subtypes. When tasks are cancelled, an AggregateException can be thrown that contains both cancellation exceptions and exceptions caused by real problems. This often means that you need to filter out the cancellation exceptions and perform clean-up operations in response to any other errors.

We can use the AggregateException's Handle method with a lambda expression to handle OperationCanceledExceptions. The Handle method can be called in a manner that causes all exceptions of other types to be re-thrown in a new AggregateException. However, this can lead to ugly code. First we must wrap an operation in a try / catch block to catch the AggregateException. We then have to call the Handle method within a second, nested try / catch block in order to catch and process the non-cancellation exceptions.

To simplify the process of removing the cancellation exceptions, you can use an extension method. In this article we will construct this extension method so that you can use it in your future projects.

Sample Code

Below is some simple sample code. Here, ten tasks are started. Nine pause for a short period before exiting normally if not cancelled. If they are cancelled, each throws an OperationCanceledException using the standard call to ThrowIfCancellationRequested. The tenth task throws an InvalidOperationException.

If you allow enough time for all of the tasks to complete before pressing the Enter key, you will see that the AggregateException contains only the InvalidOperationException. However, if you press Enter before all of the tasks exit, the AggregateException contains the InvalidOperationException and a number of cancellation exceptions. This complicates the process of identifying the real problem.

Task[] tasks = new Task[10];

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

for (int i = 0; i < 9; i++)
{
    tasks[i] = new Task(() =>
    {
        token.ThrowIfCancellationRequested();
        Console.WriteLine("Task {0} started", Task.CurrentId);
        Thread.Sleep(2000);
        token.ThrowIfCancellationRequested();
    });
    tasks[i].Start();
}

tasks[9] = new Task(() => { throw new InvalidOperationException("Invalid!"); });
tasks[9].Start();

Console.WriteLine("Press Enter to cancel");
Console.ReadLine();
tokenSource.Cancel();

try
{
    Task.WaitAll(tasks);
}
catch (AggregateException aex)
{
    foreach (var exception in aex.InnerExceptions)
    {
        Console.WriteLine(exception.Message);
    }
}

WithoutCancellations Extension Method

If you are not interested in reacting to the cancellation exceptions it would be useful to remove them from the AggregateException without having to call Handle and catch a further AggregateException. This is the purpose of the WithoutCancellations extension method shown below. It calls the Handle method for the provided AggregateException to remove cancellation exceptions. If there are any other exceptions present, these are gathered into a new AggregateException and re-thrown. The try / catch block is used to capture this new exception and return it. If only cancellation exceptions are present, no new exception is thrown so a new, empty AggregateException is constructed and returned.

public static class AggregateExceptionExtensions
{
    public static AggregateException WithoutCancellations(this AggregateException exception)
    {
        try { exception.Handle(e => { return e is OperationCanceledException; }); }
        catch (AggregateException ex) { return ex; }
        return new AggregateException();
    }
}

Testing the Extension Method

To use the extension method within the original sample code we need to make one modification. In the foreach loop that outputs the error messages we need to call WithoutCancellations and access its InnerExceptions property instead of the original exception's property. Modify the loop as follows:

foreach (var exception in aex.WithoutCancellations().InnerExceptions)

You can now run the program again. Note that the InvalidOperationException is the only inner exception present whether or not you cancel the process before the tasks have finished executing.

30 December 2011