.NET Framework Documentation

Tutorials & Guides: Asynchronous Programming

Handling Cancellation in Asynchronous Operations

Asynchronous operations can sometimes take a long time or become unnecessary. .NET provides robust mechanisms for handling cancellation, allowing you to signal to an ongoing operation that it should stop processing.

The Role of CancellationToken and CancellationTokenSource

The core of cancellation in .NET is the pair of CancellationTokenSource and CancellationToken.

Here's a basic pattern:


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

public class AsyncOperationWithCancellation
{
    public async Task DoWorkAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("Operation started...");

        for (int i = 0; i < 10; i++)
        {
            // Check for cancellation before starting a new iteration
            cancellationToken.ThrowIfCancellationRequested();

            Console.WriteLine($"Processing step {i + 1}...");
            await Task.Delay(500, cancellationToken); // Task.Delay also respects CancellationToken
        }

        Console.WriteLine("Operation completed successfully.");
    }

    public async Task RunOperation()
    {
        using (var cts = new CancellationTokenSource())
        {
            var operationTask = DoWorkAsync(cts.Token);

            // Simulate cancelling after a few seconds
            await Task.Delay(2000);
            Console.WriteLine("Requesting cancellation...");
            cts.Cancel();

            try
            {
                await operationTask;
                Console.WriteLine("Operation task finished without exception.");
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("Operation was successfully cancelled.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An unexpected error occurred: {ex.Message}");
            }
        }
    }
}
            

Checking for Cancellation

There are two primary ways an asynchronous method can respond to a cancellation request:

  1. Polling: Periodically check the CancellationToken.IsCancellationRequested property. If it returns true, the method should clean up its resources and return early.
  2. Throwing Exception: Use the convenient CancellationToken.ThrowIfCancellationRequested() method. This method throws an OperationCanceledException if cancellation has been requested. This is often preferred as it clearly signals the intent.

Many .NET asynchronous APIs, like Task.Delay, HttpClient.GetAsync, and others, accept a CancellationToken directly. When passed, these methods will automatically monitor the token and throw OperationCanceledException if cancellation is requested during their execution.

Best Practice: Always pass the CancellationToken down the call chain to operations that can potentially take a long time. This allows cancellation to propagate effectively.

Handling Cancellation Exceptions

When CancellationToken.ThrowIfCancellationRequested() is called or when a built-in API detects a cancellation request, an OperationCanceledException is thrown. Your code that calls the cancellable operation should be prepared to catch this specific exception.


try
{
    await SomeCancellableOperationAsync(cancellationToken);
}
catch (OperationCanceledException)
{
    // Handle the cancellation gracefully.
    // This is the expected outcome when cancellation is requested.
    Console.WriteLine("The operation was cancelled as requested.");
}
catch (Exception ex)
{
    // Handle other potential exceptions.
    Console.WriteLine($"An error occurred: {ex.Message}");
}
            

Note that an OperationCanceledException thrown due to cancellation will have its CancellationToken property set to the token that was used to request the cancellation. This can be useful for distinguishing between different cancellation requests if you have multiple tokens in play.

Advanced Scenarios

Mastering asynchronous cancellation is crucial for building responsive and robust applications in .NET.