Software Development

Tasks that throw exceptions

I’ve blogged before about the AggregateException in .NET 4, but I missed out something that may be important. If you are using Parallel.Invoke or Parallel.For or Parallel.ForEach or PLINQ you probably won’t notice this because each of these constructs block until all the tasks are completed. However, if you are using Task.Factory.StartNew() then this may be important.

The AggregateException won’t bubble up into the calling thread or task until one of the Wait… methods (excluding WaitAny) is called.

This means that any number of exceptions can be happening but the thread that starts the task may not know about it for some time if it is getting on with other things itself.

Take this code for example:

class Program
{
    static void Main(string[] args)
    {
        // Start the tasks
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 20; i++)
        {
            Task t = Task.Factory.StartNew(PerformTask);
            tasks.Add(t);
        }

        Console.WriteLine("Press enter to display the task status.");
        Console.ReadLine();

        // Display the status of each task.
        // If it has thrown an exception will be "faulted"
        foreach(Task t in tasks)
            Console.WriteLine("Task {0} status: {1}", t.Id, t.Status);

        Console.WriteLine("Press enter to wait for all tasks.");
        Console.ReadLine();

        // This is where the AggregateException is finally thrown
        Task.WaitAll(tasks.ToArray());

        Console.ReadLine();
    }

    public static void PerformTask()
    {
        Console.WriteLine("Starting Task {0}", Task.CurrentId);
        throw new Exception("Throwing exception in task "+Task.CurrentId);
    }
}

At the start of the program it starts 20 tasks, each of which throws an exception. The output of the program at this point is:

Press enter to display the task status.
Starting Task 2
Starting Task 1
Starting Task 4
Starting Task 3
Starting Task 5
Starting Task 7
Starting Task 6
Starting Task 8
Starting Task 9
Starting Task 10
Starting Task 11
Starting Task 12
Starting Task 13
Starting Task 14
Starting Task 15
Starting Task 17
Starting Task 16
Starting Task 18
Starting Task 19
Starting Task 20

The reason the "Press enter to display the task status" appears on the first line is that the main thread is still running and performing operations as the background tasks are still spinning up.

Then each of the tasks are started and each output a simple line to the console to prove they are there. Then each throws an exception, so far unseen (other than for the debugger breaking the running on the program to tell the developer, but in a production system you are not going to have that.)

Now, the application is paused on a Console.ReadLine() waiting for the user to press enter. The application is still running merrily.

If the enter key is pressed the next bit of output is displayed:

Task 1 status: Faulted
Task 2 status: Faulted
Task 3 status: Faulted
Task 4 status: Faulted
Task 5 status: Faulted
Task 6 status: Faulted
Task 7 status: Faulted
Task 8 status: Faulted
Task 9 status: Faulted
Task 10 status: Faulted
Task 11 status: Faulted
Task 12 status: Faulted
Task 13 status: Faulted
Task 14 status: Faulted
Task 15 status: Faulted
Task 16 status: Faulted
Task 17 status: Faulted
Task 18 status: Faulted
Task 19 status: Faulted
Task 20 status: Faulted
Press enter to wait for all tasks.

This time the "Press enter to wait for all tasks." message appears at the end of the list. This is because everything here is being written from the main thread.

As you can see everything is "Faulted" meaning that an exception was thrown. Yet, still the application is proceeding merrily along the main thread

Finally, the enter key is pressed and the Task.WaitAll() method is called…. And the main thread only how has all those exceptions to contend with (in the form of an AggregateException)

Throw on WaitAll

That’s a bit of a gotcha if you don’t know where the AggregateException is coming from.

4 thoughts on “Tasks that throw exceptions

  1. It isn’t such a nasty thing really. It provides a well defined point at which the exceptions will be raise through to the thread that invoked the tasks. Otherwise the exceptions could potentially happen anywhere and cause the software to destabilise.

    What happens is that the exceptions go into a data structure controlled by the task parallel library and when Task.WaitAll is called the exceptions in the data structured are attached to an AggregateException and that AggregateException object is thrown from within WaitAll in the context of the thread that launched the tasks but without losing valuable information by throwing the exception over again in that context.

    The real gotcha was that it may not be intuitively obvious what is happening at first. But once you understand what’s going on and why it does make sense… I hope. 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s