24

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?

Joe White
  • 94,807
  • 60
  • 220
  • 330
  • Use the overload that takes a CancellationToken. – Hans Passant Dec 30 '11 at 16:29
  • @HansPassant, the docs for that overload don't actually say what it uses the token for. Will it ignore TaskCanceledExceptions associated with that token, or will it simply return early if the token is canceled? I need it not to return until all the tasks have stopped running. – Joe White Dec 30 '11 at 16:31
  • 1
    You use the CancellationToken to cancel the tasks. And filter the specific OperationCancelException you'll now get. – Hans Passant Dec 30 '11 at 16:33

2 Answers2

28

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.

Michael
  • 8,362
  • 6
  • 61
  • 88
João Angelo
  • 56,552
  • 12
  • 145
  • 147
1

Based on João Angelo's suggestion, here goes a Task class extension

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MySharedLibrary.Extensions
{
    public static class TaskExtensions
    {

        // This code is based João Angelo's stackoverflow suggestion https://stackoverflow.com/a/8681687/378115

        // Use this when a CancellationTokenSource is used
        public static void SafeWait(this Task TargetTask, CancellationTokenSource TargetTaskCancellationTokenSource)
        {
            if (TargetTaskCancellationTokenSource.IsCancellationRequested == false)
            {
                TargetTaskCancellationTokenSource.Cancel();
            }
            SafeWait(TargetTask);
        }

        // Use this when no CancellationTokenSource is used
        public static void SafeWait(this Task TargetTask)
        {
            try
            {
                if (TargetTask.IsCanceled == false)
                {
                    TargetTask.Wait();
                }
            }
            catch (AggregateException errors)
            {
                errors.Handle(e => e is TaskCanceledException);
            }
        }

    }
}
Community
  • 1
  • 1
Julio Nobre
  • 4,196
  • 3
  • 46
  • 49