How to wait on tasks without throwing TaskCanceledExceptions

cancellationexception handlingtask-parallel-library

I have a method that creates some Tasks, and then waits on them with WaitAll before returning. The problem is, if those tasks got canceled, then WaitAll throws an AggregateException containing lots of TaskCanceledExceptions.

That means that WaitAll will throw exceptions in two different circumstances:

  • Exceptions that indicate a genuine error. These mean that there was a condition we didn't know how to handle; they need to propagate as unhandled exceptions, until they eventually terminate the process.
  • Exceptions that indicate that the user clicked a Cancel button. These mean that the task was canceled and cleaned up, and the program should continue running normally.

The latter fits squarely into the definition of a vexing exception: it's an exception thrown in a completely non-exceptional circumstance, so I have to catch it in order to resume normal control flow. Fortunately it's easy to catch, right? Just add catch (AggregateException) and — oh wait, that's the same type that gets thrown when there's a fatal error.

I do need to wait for the tasks to finish running before I return (I need to know that they're no longer using their database connections, file handles, or anything else), so I do need the WaitAll or something similar. And if any of the tasks faulted, I do want those exceptions to propagate as unhandled exceptions. I just don't want exceptions for cancel.

How can I prevent WaitAll from throwing exceptions for canceled tasks?

Best Answer

The AggregateException provides a Handle method that can be used for these situations. If for example you want to ignore TaskCanceledException you can do:

var all = new AggregateException(
    new NullReferenceException(),
    new TaskCanceledException(),
    new TaskCanceledException(),
    new InvalidOperationException(),
    new TaskCanceledException());

try
{
    throw all;
}
catch (AggregateException errors)
{
    errors.Handle(e => e is TaskCanceledException);
} 

If all the exceptions are of type TaskCanceledException, the Handle method will not throw any exception; otherwise a new AggregateException containing only the unhandled exceptions will be thrown.

Related Topic