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+

Cancelling Parallel Loops

The Task Parallel Library includes static methods that provide parallel equivalents of the for and foreach loops. As with parallel tasks, these loops can be cancelled by other processes using a system of cancellation tokens.

Cancellation Tokens

In an earlier article, as part of the "Parallel Programming in .NET" tutorial, I described the way in which parallel tasks can be cancelled. To create a task that can be cancelled, you pass it a cancellation token from a CancellationTokenSource object. Later you can call the CancellationTokenSource's Cancel method to signal to all of the tasks that use its token that they should terminate. The linked tasks detect this signal via the token and, where appropriate, stop their activity in a safe manner. The cancellation is co-operative, in that the parallel tasks are not forcibly terminated. You create the code that ensures that the system is in a valid state before the task stops. Sometimes this means that the task continues to completion, ignoring the cancellation request.

Parallel loops support the same cancellation token mechanism as parallel tasks. You can even use the same token in a loop as in one or more tasks, allowing their co-ordinated cancellation. In this article we will demonstrate loop cancellation with a sample Windows Forms application. We will use a parallel for loop that can be cancelled. The principles are exactly the same for a parallel foreach loop.

Sample Project

The example software is a simple Windows Forms project. It shows a progress bar that increases in value as a parallel loop executes and a button that cancels the loop, stopping the bar's progress. You can download the sample using the link at the top of the article or create it by following the step-by-step instructions described here. To begin, create a new Windows Forms project and add the following three controls to the automatically generated form.

Control NameTypeSpecial PropertiesPurpose
ProgressProgressBarStyle = MarqueeDisplays an increasing progress value as the parallel loop executes.
StartProcessButtonButtonText = "Start"Allows the loop to be started.
CancelProcessButtonButtonText = "Cancel"
Enabled = false
Allows the loop to be cancelled.

As we will be using classes from the System.Threading and System.Threading.Tasks namespaces, add the following two using directives to the form's code:

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

Adding Class Scope Variables

When performing the cancellation of the parallel loop, we will use a cancellation token source in several methods, including the one that contains the loop and the method that performs the cancellation. In order that the token source is visible wherever needed, we will create it as a class-level variable. We also need to use locking at one point in the program so will create an object to co-ordinate this.

Add the following two declarations to the form's class:

CancellationTokenSource _tokenSource;
object _lock = new object();

Creating the Start Button Code

The code for the Start button needs to perform several actions before the parallel loop commences. Firstly, we will disable the Start button as we do not want the user to start the process more than once. Next, we will enable the Cancel button so that the process can be terminated. To complete the initialisation we will change the progress bar's value to zero and switch the style of the bar from the marquee style to the continuous layout.

To do this, add the following to the Start button's click event code:

StartProcessButton.Enabled = false;
CancelProcessButton.Enabled = true;
Progress.Value = 0;
Progress.Style = ProgressBarStyle.Continuous;

To prepare for starting the loop we need to initialise the cancellation token source. Add the following line to the Start button's click event:

_tokenSource = new CancellationTokenSource();

Finally, we need to start the parallel loop. The code for the loop and to update the progress bar will be held in a separate method, named "DoLongRunningProcess". In order that the loop can execute without blocking the user interface (UI) thread, causing the program to hang, we need to call this method in a separate thread. We will do this by creating and starting a new parallel task.

Complete the Start button's click event code by adding the code for the task:

var task = new Task(() => DoLongRunningProcess());
task.Start();

Creating the DoLongRunningProcess Method

The DoLongRunningProcess method holds the parallel for loop and demonstrates how it can be cancelled. To keep the example focussed, the loop has one hundred iterations, each of which increments the value of the progress bar and pauses for one tenth of a second. If we were using a sequential for loop the progress bar would take ten seconds to be filled. However, as the loop operates in parallel it should be substantially quicker than this.

Start by creating the signature for the method:

private void DoLongRunningProcess()
{
}

You cannot provide a cancellation token directly to a parallel loop. It must be added to a ParallelOptions object by setting the CancellationToken property. The following two lines of code create the ParallelOptions variable.

ParallelOptions options = new ParallelOptions();
options.CancellationToken = _tokenSource.Token;

The ParallelOptions object is passed to the for loop's third parameter, following the start and end values for the loop but before the lambda expression that defines its body. The loop is shown below. Note the use of the Invoke method to change the progress value. This is required as the parallel loop will not be executing on the UI thread. If we tried to change the progress bar's value directly we would encounter a cross-threading exception. Note also the use of the lock statement. As the Value property is not guaranteed to be thread-safe, the lock statement ensures that the value will be incremented correctly one hundred times, should the loop not be cancelled.

Parallel.For(1, 101, options, i =>
{
    Thread.Sleep(100);

    lock (_lock)
    {
        Invoke((Action)delegate { Progress.Value++; });
    }
});
26 December 2011