CancellationToken in C#

CancellationToken in C#

As I promised, in this Article we will discover CancellationToken and all its reated types and usage.

The CancellationToken in C# is a powerful feature of the .NET framework used to propagate notifications that an operation should be canceled. It allows cooperative cancellation between threads, tasks, and asynchronous operations. This mechanism is particularly useful for long-running operations where it may be necessary to stop the operation gracefully before it completes.

You will find the sample codes for this article in the following link in my GitHub:

https://github.com/amirdoosti6060/CancellationTokenDemo?

How CancellationToken Works

The CancellationToken class doesn’t directly cancel anything by itself. Instead, it acts as a notification mechanism. The developer must implement the actual cancellation logic by checking the token's state periodically and then gracefully terminating the task or operation if cancellation is requested.

For a while forget about CancellationToken and suppose we want to break a simple “while loop” or in other word we want to cancel the action we are doing in the loop. In this scenario we can simply use a variable and change its value to cancel the loop:

bool myCancelationToken = false;

while (!myCancellationToken)
{
     …
     If (condition) then myCancellationToken = true;
     …
}        

In this code myCancellationToken variable doesn’t cancel anything directly but by changing it to true anywhere in the code, we signal the loop that should be cancelled.

Common Use Cases

  • Canceling long-running tasks (e.g., database operations, file I/O).
  • Stopping asynchronous operations in response to user input.
  • Gracefully terminating threads or operations.

CancellationTokenSource and CancellationToken

CancellationTokenSource:

  • It creates and manages CancellationToken instances.
  • A token source can issue cancellation signals to all associated CancellationToken objects.

CancellationToken:

  • Passed to methods or tasks to monitor whether a cancellation has been requested.


Key Methods and Properties of CancellationToken

  • IsCancellationRequested: This property is true if cancellation has been requested.
  • ThrowIfCancellationRequested(): Throws an OperationCanceledException if the cancellation is requested.
  • CanBeCanceled: Indicates if the token can ever be canceled.
  • Register(Action): Registers a delegate that is invoked when the token is canceled.


Key Methods of CancellationTokenSource

  • Cancel(): Requests cancellation of all operations associated with the tokens.
  • CancelAfter(TimeSpan): Requests cancellation after a delay.
  • Token: Returns the associated CancellationToken.

Supporting Cooperative Cancellation

Cancellation in C# is cooperative. This means the operations that can be canceled must support CancellationToken and check for cancellation periodically.

Best Practices for Using CancellationToken:

  1. Check Periodically: Always check the cancellation token at logical points in your method, such as after each major step or iteration of a loop.
  2. Use ThrowIfCancellationRequested(): For async or task-based code, throwing an OperationCanceledException allows for a consistent way to handle cancellation.
  3. Propagate the Token: Pass the CancellationToken to every async method that supports cancellation.

Creating and Using CancellationToken

The following example shows how to typically create and use CancellationToken:

CancellationToken token = cts.Token;
CancellationTokenSource cts = new CancellationTokenSource();

// Cancel after a delay of 2 seconds
cts.CancelAfter(TimeSpan.FromSeconds(2));

Console.WriteLine("Process is started and will cancel after 2 seconds ...");

// Checking for cancellation within a long-running task
await Task.Run(() =>
{
    for (int i = 0; i < 10; i++)
    {
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("Task was cancelled.");
            return;
        }

        // Simulate work
        Thread.Sleep(500);
        Console.WriteLine($"Task iteration {i}");
    }
}, token);        

Usage in Asynchronous Methods

CancellationToken is often used with async and await to cancel asynchronous operations. Here’s a typical scenario:

public async Task ProcessDataAsync(CancellationToken cancellationToken)
{
    for (int i = 0; i < 100; i++)
    {
        // Periodically check if cancellation is requested
        cancellationToken.ThrowIfCancellationRequested();

        // Simulate an asynchronous task
        await Task.Delay(100, cancellationToken);
        Console.WriteLine($"Processing data {i}% complete.");
    }
}

public async Task RunAsync()
{
    CancellationTokenSource cts = new CancellationTokenSource();

    // Example: User cancels after 1 second
    cts.CancelAfter(TimeSpan.FromSeconds(1));

    try
    {
        await ProcessDataAsync(cts.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Operation was canceled.");
    }
}        

Example: Canceling Long-Running I/O Bound Task

Consider a file download operation that may need to be canceled mid-way.

public async Task DownloadFileAsync(string url, string destinationPath, CancellationToken cancellationToken)
{
    using (var httpClient = new HttpClient())
    using (var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
    using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true))
    using (var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken))
    {
        byte[] buffer = new byte[8192];
        int bytesRead;

        while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
        {
            await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
            cancellationToken.ThrowIfCancellationRequested();
        }
    }
}        

Handling Canceled Tasks

If a task is canceled via a CancellationToken, it will usually throw an OperationCanceledException. Here’s an example of handling that exception:

public async Task DownloadMountainImage()
{
    var url = @"https://upload.wikimedia.org/wikipedia/commons/6/6b/Big_Four_Mountain.jpg";
    var destinationPath = Path.Combine(Directory.GetCurrentDirectory() + "mountain.jpg");
    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken token = cts.Token;

    // Cancel after a delay of 2 seconds
    cts.CancelAfter(TimeSpan.FromMilliseconds(50));

    try
    {
        Console.WriteLine("Start downloading mountain image ...");
        await DownloadFileAsync(url, destinationPath, token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Download was canceled.");
    }

}        

Linked CancellationTokenSource

Sometimes, multiple cancellation sources are involved. You can create a CancellationTokenSource that is linked to multiple tokens. In this case they act as OR which means if one of them get signal, then the operation should cancel:

CancellationTokenSource cts1 = new CancellationTokenSource();
CancellationTokenSource cts2 = new CancellationTokenSource();

CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);

Task.Run(() => {
    // Some work that can be canceled by either cts1 or cts2
}, linkedCts.Token);

// Cancel from either source
cts1.Cancel();        

Timeout-based Cancellation

You can use CancelAfter to automatically cancel an operation after a specific duration:

Pros and Cons of CancellationToken

CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));  // Auto-cancel after 10 seconds

try
{
     await LongRunningOperation(cts.Token);
}
catch (OperationCanceledException)
{
     Console.WriteLine("The operation timed out and was canceled.");
}        

Pros:

  • Graceful Task Cancellation: It allows operations to be canceled in a controlled and cooperative way, ensuring that resources can be cleaned up properly.
  • Supports Task-based and Event-based APIs: It's flexible enough to support various programming models, including async/await, threads, and event-driven tasks.
  • Timeouts and Linked Tokens: It offers advanced capabilities like timeouts and linking multiple token sources.

Cons:

  • Cooperative: It requires explicit support within the code to check and handle cancellation.
  • Not Forceful: Unlike thread aborts, this method requires that each operation checks for cancellation and handles it gracefully. It doesn’t forcibly stop a task.

Conclusion

CancellationToken is a powerful tool in the .NET framework for handling cancellation in a cooperative and controlled manner. By passing a CancellationToken through your asynchronous methods, you enable the ability to gracefully stop operations, clean up resources, and provide better user experience for cancelable long-running tasks.

In complex operations, proper use of CancellationTokenSource and CancellationToken is essential for building responsive, cancellable, and scalable applications.

#cancellation #canellationtoken #signaling #async #task #csharp


Good description of cancellation tokens.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了