C# > Asynchronous Programming > Parallel Programming > CancellationToken
Parallel.ForEach with CancellationToken
This snippet shows how to use Parallel.ForEach
to process a collection in parallel while also respecting a CancellationToken
. It demonstrates how to cancel the parallel loop based on an external cancellation signal.
Code Snippet
This code defines a `Main` method that creates a list of integers and then uses `Parallel.ForEach` to process them in parallel. A `CancellationTokenSource` and its token are created. The `ParallelOptions` are configured with the cancellation token and a maximum degree of parallelism (4 in this case). Inside the loop, the code simulates some work and checks for cancellation. If cancellation is requested, `token.ThrowIfCancellationRequested()` is called, which throws an `OperationCanceledException` to break out of the loop. The `try-catch` block handles the exception and ensures that the `CancellationTokenSource` is disposed of in the `finally` block.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class ParallelCancellationExample
{
public static void Main(string[] args)
{
// Create a list of items to process
List<int> items = new List<int>();
for (int i = 0; i < 20; i++)
{
items.Add(i);
}
// Create a CancellationTokenSource
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// Start the parallel loop
try
{
Parallel.ForEach(items, new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = 4 }, item =>
{
Console.WriteLine($"Processing item: {item}, Thread: {Thread.CurrentThread.ManagedThreadId}");
// Simulate some work
Thread.Sleep(100);
// Check for cancellation
if (token.IsCancellationRequested)
{
Console.WriteLine($"Cancellation requested for item: {item}");
token.ThrowIfCancellationRequested(); // Throw an exception to stop the loop
}
});
Console.WriteLine("Parallel loop completed.");
}
catch (OperationCanceledException)
{
Console.WriteLine("Parallel loop was cancelled.");
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.Message}");
}
finally
{
cts.Dispose();
}
Console.WriteLine("Program finished.");
}
}
Concepts Behind the Snippet
This snippet showcases the following concepts:
Real-Life Use Case
Consider processing a large image in parallel, where each part of the image is processed by a separate thread. If the user cancels the operation, the parallel processing should be stopped immediately to save resources. This can be achieved using a CancellationToken
.
Best Practices
CancellationToken
to parallel operations when cancellation is a possibility.token.ThrowIfCancellationRequested()
to immediately stop the parallel loop when cancellation is requested.OperationCanceledException
to ensure resources are released and the application remains stable.CancellationTokenSource
in a `finally` block to prevent resource leaks.MaxDegreeOfParallelism
to limit the number of threads used by the parallel loop.
Interview Tip
Be prepared to explain the difference between TaskCanceledException
and OperationCanceledException
. TaskCanceledException
is thrown when an asynchronous task is cancelled, while OperationCanceledException
is a more general exception that can be thrown by any operation that supports cancellation using a CancellationToken
.
When to Use Them
Use Parallel.ForEach
with CancellationToken
when you have a collection of items that can be processed independently and in parallel, and you need to provide the ability to cancel the processing if needed. This is useful in scenarios where performance is critical and cancellation is a common occurrence.
Memory Footprint
The memory footprint depends on the size of the collection being processed and the degree of parallelism. Each thread in the parallel loop will require its own stack and other resources. However, the overhead of the `CancellationToken` and `ParallelOptions` is relatively small.
Alternatives
Alternatives to using Parallel.ForEach
with CancellationToken
include:
Parallel.ForEach
.CancellationToken
.
Pros
Cons
FAQ
-
What happens if I don't handle the OperationCanceledException?
If you don't handle the `OperationCanceledException`, the exception will propagate up the call stack and may crash the application. It's important to catch the exception and handle it gracefully. -
How do I know if the parallel loop completed successfully or was cancelled?
If the parallel loop completes without cancellation, the code after the `Parallel.ForEach` call will be executed. If the loop is cancelled, the `OperationCanceledException` will be thrown. You can check for the presence of the exception to determine whether the loop was cancelled. -
Can I update shared variables inside the parallel loop?
Yes, but you need to use appropriate synchronization mechanisms, such as locks or atomic operations, to prevent race conditions and ensure data integrity.