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+

Waiting for Parallel Tasks to Complete

The ninth part of the Parallel Programming in .NET tutorial considers that the parallel nature of tasks means that it is not possible to assume that tasks have completed. It describes how to synchronise tasks and capture their unhandled exceptions.

Synchronisation

When you are developing software that uses the Task class to include parallel operations, you will often have situations where a task must be completed before the main thread can continue processing. This may be because the parallel task generates results that are needed later in the process and you must wait for the results to be available before you attempt to use them. The parallel tasks must therefore be synchronised for the correct operation of the software.

The Task Parallel Library (TPL) includes several methods that allow you to wait for one or more parallel tasks to complete before continuing processing. In this article we will examine three such methods. They are all members of the Task class, which resides in the System.Threading.Tasks namespace. We will also be using the Thread class from the System.Threading namespace in order to simulate long-running tasks. To follow the samples, create a new console application project and add the following using directives to the initial class.

using System.Threading;
using System.Threading.Tasks;

Task.Wait Method

Often you will wish to wait for a single task to complete, either successfully or with an exception, before continuing. This can be achieved with the Task class' Wait method. In the most basic situation this blocks the thread from which it is called until the task being waited for stops. Once the task completes or throws an exception, the primary thread restarts.

As discussed earlier in the tutorial, the scheduling of tasks by the TPL is quite sophisticated. This is the case with the Wait method. Although it can be assumed that the main thread is blocked whilst a parallel task finishes its work, this is not always the case. If there is enough processor capability available and the task being waited for has not yet started, the task may be transferred to the main thread for processing. This can be more efficient, as a thread does not need to be blocked, and can mean that the task is started sooner than expected.

To demonstrate the use of the Wait method, first consider the following sample code. Here we start a task that simulates retrieving some numeric data from an external data source. The task shows a message, pauses for five seconds and then returns an array of ten integers. The main thread uses the results of the task by summing them and outputting the result.

When you run the program you will see that it throws an ArgumentNullException. This is caused by trying to use the array before it has been populated by the parallel task.

int[] values = null;

Task loadDataTask = new Task(() =>
{
    Console.WriteLine("Loading data...");
    Thread.Sleep(5000);
    values = Enumerable.Range(1,10).ToArray();
});
loadDataTask.Start();

Console.WriteLine("Data total = {0}", values.Sum());    // ArgumentNullException

We can rectify the problem quite easily by calling the task's Wait method. When used with no arguments the method blocks the current thread until the task completes, no matter how long this takes:

int[] values = null;

Task loadDataTask = new Task(() =>
{
    Console.WriteLine("Loading data...");
    Thread.Sleep(5000);
    values = Enumerable.Range(1,10).ToArray();
});
loadDataTask.Start();
loadDataTask.Wait();
loadDataTask.Dispose();

Console.WriteLine("Data total = {0}", values.Sum());    // Data total = 55

Exception Handling

When an exception is thrown during the execution of a task, and it is not handled within that task, the exception is deferred for later observation. In fact, if you do not specifically test for an exception it may be lost altogether.

We can see the loss of an exception by adding the following code to the Main method of a console application project. Here we start a task that shows two messages before throwing an exception. If you run the program without debugging, you will find that the InvalidOperationException is never seen.

Task loadDataTask = new Task(() =>
{
    Console.WriteLine("Loading data...");
    Thread.Sleep(5000);
    Console.WriteLine("About to throw Exception");
    throw new InvalidOperationException();
});
loadDataTask.Start();
Console.ReadLine();

The unhandled exceptions from a task are observed when a Wait method is encountered. The Wait method blocks the current thread until the task completes. If the task runs without error, control passes to the command following the Wait call. If an unhandled exception occurs, the exception is wrapped within an AggregateException and thrown at the point of the Wait. To handle these exceptions you should therefore wrap the Wait call in a try / catch block that catches AggregateExceptions.

The updated sample below adds exception handling to the Wait method. When an AggregateException is caught, the messages from its InnerExceptions property are displayed.

Task loadDataTask = new Task(() =>
{
    Console.WriteLine("Loading data...");
    Thread.Sleep(5000);
    throw new InvalidOperationException();
});
loadDataTask.Start();

try
{
    loadDataTask.Wait();
}
catch (AggregateException ex)
{
    foreach (var exception in ex.InnerExceptions)
    {
        Console.WriteLine(exception.Message);
    }
}

loadDataTask.Dispose();

/* OUTPUT

Loading data...
Operation is not valid due to the current state of the object.

*/
4 October 2011