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.

Adding Timeouts

If there is a possibility of a task running for too long, you can provide a timeout value using an overloaded version of the Wait method. The timeout can be provided as an integer, where it represents a number of milliseconds, or as a TimeSpan value. In either case, a value of -1 milliseconds removes the timeout and blocks the main thread indefinitely.

If the task completes before the timeout expires, the main thread is unblocked as you would expect and the Wait method returns true. If the task does not finish in time, the main thread is unblocked and the method returns false. However, this does not stop the parallel task, which may complete at a later time. If a task terminates with an unhandled exception after the timeout has expired, the exception may be lost.

The example below shows the Wait used with a timeout of ten seconds for a task that executes in around half this duration. The Wait method returns true and the Sum operation is carried out.

int[] values = null;

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

if (loadDataTask.Wait(10000))
{
    Console.WriteLine("Data total = {0}", values.Sum());
    loadDataTask.Dispose();
}
else
{
    Console.WriteLine("Data read timeout");
}
 
/* OUTPUT
 
Loading data...
Data total = 55

*/

The next example is similar but uses a TimeSpan value for the timeout. In this case the timeout is too short to allow the task to complete in time.

int[] values = null;

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

if (loadDataTask.Wait(TimeSpan.FromSeconds(3)))
{
    Console.WriteLine("Data total = {0}", values.Sum());
    loadDataTask.Dispose();
}
else
{
    Console.WriteLine("Data read timeout");
}

/* OUTPUT

Loading data...
Data read timeout

*/

NB: In the above examples the Dispose method has been included only when the task completes within the timeout period. Disposing the task when it times out risks an exception because an incomplete task may not be disposed. In these cases we are relying on the garbage collector to dispose the tasks later.

Waiting for Multiple Tasks

Although it is possible to use multiple Wait calls to wait for a number of parallel tasks to complete, it is more elegant to use a single method for this purpose. There are two methods that permit you to do so, each a static member of the Task class and each accepting a parameter array of tasks that you wish to wait for and an optional timeout value. The first of these is WaitAll. As the name suggests, this method blocks the main thread until all of the tasks passed to its parameter have completed or have thrown exceptions.

In the sample code below, two tasks are started, each simulating loading some data from a data source. The WaitAll call ensures that both of these tasks finish before the final message is displayed.

Task loadUserDataTask = new Task(() =>
{
    Console.WriteLine("Loading User Data");
    Thread.Sleep(2000);
    Console.WriteLine("Employee User loaded");
});

Task loadCustomerDataTask = new Task(() =>
{
    Console.WriteLine("Loading Customer Data");
    Thread.Sleep(2000);
    Console.WriteLine("Employee Customer loaded");
});

loadUserDataTask.Start();
loadCustomerDataTask.Start();

Task.WaitAll(loadUserDataTask, loadCustomerDataTask);

loadUserDataTask.Dispose();
loadCustomerDataTask.Dispose();

Console.WriteLine("All data loaded");

/* OUTPUT

Loading Customer Data
Loading User Data
Employee Customer loaded
Employee User loaded
All data loaded

*/

If you start multiple tasks but only need one of them to complete before continuing processing you can use the WaitAny method. As with WaitAll, the tasks are passed to the method's array parameter. Processing continues as soon as the first of the tasks ends. This is demonstrated below where the two tasks simulate checks that two servers are available. As soon as one of the servers is found, the main thread is unblocked.

NB: The other tasks will still continue to execute and it is possible for exceptions that they throw to be overlooked.

Task server1Task = new Task(() =>
{
    Console.WriteLine("Checking Server 1");
    Thread.Sleep(4000);
    Console.WriteLine("Server 1 OK");
});

Task server2Task = new Task(() =>
{
    Console.WriteLine("Checking Server 2");
    Thread.Sleep(2000);
    Console.WriteLine("Server 2 OK");
});

server1Task.Start();
server2Task.Start();

Task.WaitAny(server1Task, server2Task);

Console.WriteLine("One server checked");

/* OUTPUT

Checking Server 1
Checking Server 2
Server 2 OK
One server checked

*/

NB: The tasks are not disposed in this example as one task will still be running and you should not try to dispose incomplete tasks.

4 October 2011