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+

Task Cancellation

The fourteenth part of the Parallel Programming in .NET tutorial examines how parallel tasks are cancelled. This includes stopping single tasks, co-ordinating the cancellation of multiple tasks and dealing with tasks that are cancelled before they start.

Cancellation Tokens

When you are performing a long-running process in single-threaded software, it is usually simple to cancel it. When you are developing parallel applications, where multiple threads may be running simultaneously, it can be much more difficult to co-ordinate the cancellation of several related processes. For example, in a Windows application you may have a number of tasks, each performing a file operation. If the user clicks the "Cancel" button, you might wish to cancel all of those tasks whilst ensuring that no data is lost and all files are correctly closed and their objects disposed.

The Task Parallel Library simplifies task cancellation using cancellation tokens. When a task is instantiated, a token can be provided. From outside the task another process signal that cancellation is required and this request is transferred via the token. The code in the task can check the token and gracefully exit after performing any clean-up operations. Furthermore, the cancellation token can be shared by multiple parallel tasks, allowing one request for cancellation to halt the execution of any number of tasks.

In this article we will see several examples of the use of cancellation tokens to cancel tasks. We'll begin by building up a standard pattern for task cancellation in several steps. To recreate the examples, create a new console application. As the Task classes are found in the System.Threading.Tasks namespace and the cancellation token types are in System.Threading, add the following using directives to your code:

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

Cancelling a Task

There are several requirements to permit the cancellation of a task. First, you need to generate a token that will help to co-ordinate cancellation. The token itself is an instance of the CancellationToken structure. The token is not instantiated using a constructor. Instead, you create an instance of the CancellationTokenSource class and read the token from its Token property.

When creating tasks that support cancellation, you pass the token to the Task's constructor in addition to the delegate to execute. The token also needs to be accessible within the delegate so that you can access its properties and methods. These include the IsCancellationRequested property, which returns a Boolean value indicating whether cancellation has or has not been requested.

The final part of supporting cancellation is ensuring that your task can stop gracefully and leave the system in a valid state. Within long-running tasks you should periodically check if the task has been cancelled and, if it has, do any clean-up work before exiting. This may include closing files or database connections, completing or rolling back transactions and disposing of resources. It should be noted that requesting cancellation will never force a task to halt. If you ignore a cancellation request your task will continue executing until complete or until it throws an exception.

We can see this pattern in our first example below. In the Main method we create a CancellationTokenSource and use it to obtain a token. We then pass this token to our Task's constructor. In this case the lambda expression that defines the Task's actions calls a separate method. This is to make the structure of the task cancellation pattern easier to see. After the task has started, we wait for the user to press Enter before cancelling the task by calling the CancellationTokenSource's Cancel method. This tells any tasks that used its tokens that they should stop executing.

The DoLongRunningTask method is called from within the parallel task. It simulates a long running process by looping one hundred times and pausing for one second between each iteration. Before the loop starts, the IsCancellationRequested property of the token is checked. As it is possible that the task could have been cancelled before it had actually started executing, this check allows it to be stopped without performing any work. The IsCancellationRequested flag is checked again during each iteration. If true, a message is displayed and the loop exits.

Try running the code and allowing a few iterations to complete before pressing Enter to cancel the task.

static void Main()
{
    var tokenSource = new CancellationTokenSource();
    var token = tokenSource.Token;
    var task = new Task(() => DoLongRunningTask(token), token);

    Console.WriteLine("Press Enter to cancel");
    task.Start();

    Console.ReadLine();
    tokenSource.Cancel();
    task.Wait();
    task.Dispose();

    Console.WriteLine("Press Enter to exit");
    Console.ReadLine();
}

static void DoLongRunningTask(CancellationToken token)
{
    if (token.IsCancellationRequested)
    {
        Console.WriteLine("Cancelled before long running task started");
        return;
    }

    for (int i = 0; i <= 100; i++)
    {
        Console.WriteLine("{0}%", i);
        Thread.Sleep(1000);

        if (token.IsCancellationRequested)
        {
            Console.WriteLine("Cancelled");
            break;
        }
    }
}
26 November 2011