
.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);
}
}
30 December 2011