Cancellation Token

Harnessing the Power of CancellationToken for Efficient and Safe Asynchronous Programming

Asynchronous programming can be challenging, especially when it comes to managing long-running operations or dealing with unpredictable user interactions. It is crucial to handle cancellations gracefully to ensure that the application remains responsive and doesn't waste resources on abandoned work. One of the techniques for efficiently and safely managing these scenarios is the use of the 'CancellationToken' class. By accepting a CancellationToken as a parameter, a method can be notified when the caller is no longer interested in the result and can gracefully cancel its work.

A 'CancellationToken' is an object that allows you to communicate the cancellation status to an asynchronous operation. It's a way of telling an operation to stop executing if it's no longer needed. For example, if a user cancels a search operation in a web application, you can use a 'CancellationToken' instance to signal to the server to stop processing the request.

One way to use a 'CancellationToken' is with the Task class in C#. The Task class represents an asynchronous operation that can be canceled using a 'CancellationToken' . To use it, you simply pass a 'CancellationToken' to the Task.Run method, which runs the operation asynchronously.

The following code snippet shows how to use a 'CancellationToken' with the Task class:

public async Task DownloadFileAsync(string url,
  string filename,
  HttpClient client,
  CancellationToken cancellationToken)
{
  await client.DownloadFileTaskAsync(url, filename, cancellationToken);
}

In this example, the DownloadFileAsync method takes a URL and a filename as input, along with a 'CancellationToken' . It accepts an instance of the HttpClient class, which is used to download the file. The DownloadFileTaskAsync method is called on the client object, passing in the URL, filename, and cancellationToken. If the 'CancellationToken' is canceled before the download is complete, the Task will be canceled.

By using a 'CancellationToken' , you can make your code more responsive to the requestor. Most asynchronous libraries allow for the passing of the 'CancellationToken' in their methods.

Checking For Cancellation

When you're writing asynchronous code, it's important to handle cancellation gracefully. This is especially true when you're dealing with long-running operations or unpredictable user interactions. One way to handle cancellation is by using a CancellationToken. A CancellationToken is an object that allows the requesting code to signal a cancellation of an asynchronous operation. It's a way of telling an operation to stop executing if it's no longer needed. In your code, you can use a CancellationToken to signal to the server to stop processing the request. You can call the 'ThrowIfCancellationRequested()' method to check if the CancellationToken has been canceled and throw an exception if it has. Determining where in your business logic to place the call to 'ThrowIfCancellationRequested()' can be a crucial decision to ensure efficient and safe asynchronous programming.

First, you need to identify the points in your code where cancellation is allowed. This could include inside loops, I/O operations, and any long-running or expensive operations. Once you have identified these critical points, you should determine how you want to handle cancellation at each point. For example, you may choose to cancel and abort an operation immediately or allow it to complete gracefully.

Next, you should consider the granularity of cancellation. It's important to strike a balance between being too granular and being too coarse. If you're too granular, you risk overhead from multiple cancellation requests that occur in quick succession. On the other hand, if you're too coarse, you may not be able to cancel the operation at the desired point.

After deciding on the granularity of cancellation, you should then decide where in your business logic to place the call to 'ThrowIfCancellationRequested()' . Ideally, you should place it in a way that ensures cancellation occurs as close to the critical point as possible while still providing sufficient time for cleanup and graceful shutdown.

public async Task DownloadFileAsync(list<string> urls,
  string filename,
  HttpClient client,
  CancellationToken cancellationToken)
  {
    try {
      foreach (var url in urls)
      {
        // Check for cancellation before each download
        cancellationToken.ThrowIfCancellationRequested();
        await client.DownloadFileTaskAsync(url, filename, cancellationToken);
      }
    }
    catch (TaskCanceledException ex)
    {
      // Handle cancellation thronw by client.DownloadFileTaskAsync() here
    }
    catch (OperationCanceledException ex)
    {
      // Handle cancellation thronw by ThrowIfCancellationRequested() here
    }
  }

Overall, careful consideration of where to place the call to 'ThrowIfCancellationRequested()' in your business logic is critical to ensure efficient and safe asynchronous programming. Keep in mind that calling the 'ThrowIfCancellationRequested()' method only checks if the cancellation has been requested and throws an exception of type OperationCanceledException if necessary. This method does not change the state of the cancellation token.

It's also worth noting that if you need to check the state of the cancellation token without throwing an exception, you can use the IsCancellationRequested property. This property returns a boolean value indicating whether cancellation has been requested for the token.

It is important to consider the consequences of cancellation, especially in long-running or expensive operations. You should ensure that any resources acquired during the operation are properly released and any partial results are correctly handled in the event of a cancellation.

In general, it's important to understand the behavior of CancellationToken and related classes when writing asynchronous code. By following best practices like propagating cancellation tokens and handling cancellation exceptions properly, you can write robust and reliable asynchronous code that can respond appropriately to user or system requests for cancellation.

If you're looking for more in-depth examples of how to use CancellationToken and other asynchronous techniques in .NET, you may want to check out the AsyncDemo repository on GitHub, which contains sample code and projects that demonstrate these concepts in action.

Check for cancellation after the "point of no cancellation": If your method has already incurred side effects that can't be reverted, it's essential to ensure that canceling the operation won't leave the application in an inconsistent state. If the cancellation token is signaled, you should only cancel the operation when you can do so, leaving objects in a valid state. This may require you to finish a large amount of work, undo previous work, or find a convenient place to stop before throwing OperationCanceledException.

Pass the CancellationToken to all methods: Propagate the CancellationToken to all the methods you call that accept one, except after the point of no cancellation. This ensures that every method involved in the operation can respond appropriately to cancellation requests.

Handle exceptions thrown when an operation or task is canceled: When a cancellation is requested, the operation or task may throw an OperationCanceledException or TaskCanceledException. It's important to catch these exceptions and handle them appropriately. To ensure that you catch all cancellation exceptions, catch OperationCanceledException instead of TaskCanceledException.

Sample Code:

For more complete coding examples of asynchronous techniques in .NET, check out AsyncDemo repository on GitHub. This repository, developed by Mark Hazleton, provides a wide range of examples of how to use CancellationToken and other asynchronous programming techniques in a safe and efficient manner.

Conclusion:

By following best practices for handling cancellations in asynchronous programming, you can ensure that your application remains responsive, efficient, and free from errors caused by abandoned work. Use CancellationToken to gracefully handle cancellations, pass it to all methods involved in the operation, and handle exceptions thrown when an operation or task is canceled. With these practices in place, you can write reliable and efficient asynchronous code.

Related Articles