Cancellation Tokens in C#
Tags: csharp, dotnet, learning
Published: 2024-06-11 19:21:17

## Hi There! 👋🏻
It's been a while since I wrote something about C#, or wrote anything in general, but now I have an interesting concept that I'm sure you've stumbled upon or seen somewhere in a C# code base. 
If you used Entity Framework, HttpClient or anything that have methods which can do asynchronous work, it's almost guaranteed an overload accepting a `CancellationToken` was present there.

So what are cancellation tokens? Why are they used? What's the benefit of them? Buckle up, because that's what we are going to answer in this post!

## Defining Cancellation Tokens 🪙
A cancellation token in C# is a readonly struct, which is defined as the following according to Microsoft documentation:
> 🔔 Propagates notification that operations should be canceled.

What that means is, a cancellation token is an object used to tell if an operation should be cancelled before it's done executing. 
This mechanism is definitely something you must be aware of, and should have an idea of how to use it and when.

If you're not convinced of its importance, I want to demonstrate a real example that should be sufficient to hook you and make you a massive proponent of using cancellation tokens whenever possible.


Imagine you visit a website like Amazon, you navigate to a certain product category, and once you do, Amazon begins loading a bunch of products from that category, but imagine you pressed on a wrong category, and you immediately navigated back. Assuming Amazon doesn't use the concept of task cancellation in general, the website will still continue to process the request even though you navigated back, the task was just not cancelled, and the database query will still run and return data that will never be used. 

This in essence, is wasted application resources. The app had to perform a potentially demanding database query, to return the retrieved data nowhere. This not only applies to data retrieval, it could be any other operation, like a network call to a remote API, or an IO operation.

## The origin of CancellationToken in **C#** 💡
Now that you're likely convinced that you SHOULD incorporate cancellation tokens in your asynchronous C# code, let's get into the C# specifics regarding this concept.

In C#, to get a cancellation token, we need to create an instance of `CancellationTokenSource`, and through that source object, we can obtain the associated cancellation token by using the `Token` property on that object. The following code demonstrates the creation of a `CancellationToken` in a C# console app:

```csharp

CancellationTokenSource cts = new();

CancellationToken cancellationToken = cts.Token;

```
A little bit about the `CancellationTokenSource`, it's the object that will signal to a cancellation token that it should be cancelled, it could be immediate cancellation by calling a cancellation token's `Cancel` method, or `CancelAfter` to specify when the token should switch to the cancelled state.

The source class, has 4 different constructors:
1. `CancellationTokenSource()`
1. `CancellationTokenSource(int millisecondsDelay)`
1. `CancellationTokenSource(TimeSpan delay)`
1. `CancellationTokenSource(TimeSpan delay, TimeProvider timeProvider)`

To sum them up, all the ones that do have a delay, will create an instance that will signal cancellation on the instance's cancellation token after the provided delay in the constructor.

### A basic coding sample
We've talked theory, but now I want to show you a basic code snippet, that should sort of illustrate the concept code-wise.

```csharp

CancellationTokenSource cts = new(3000);
CancellationToken cancellationToken = cts.Token;

Task lazyCountingTask = Task.Run(() => LazyCounterFunction(cancellationToken), cancellationToken);

try
{
    await lazyCountingTask;
}
catch (OperationCanceledException ex)
{
    Console.WriteLine("Cancellation Was Requested! Lazy counter defeated");
}

static async Task LazyCounterFunction(CancellationToken cancellationToken)
{
    int counter = 0;
    while (true)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            Console.WriteLine("WE'VE BEEN COMMANDED TO CANCEL!!!");
            cancellationToken.ThrowIfCancellationRequested();
        }

        Console.WriteLine($"Counting... Currently At: {counter++}");
        await Task.Delay(1000);
    }
}

```

The code provided creates a counter function, which increments a counter value every second starting from 0. Inside the while loop, we check the `IsCancellationRequested` property on the cancellation token instance, so that when cancellation is triggered, we log a message to the console, and then call `ThrowIfCancellationRequested()` on that token. This method will throw an `OperationCancelledException`, which the calling code can catch to execute some logic in case the task was cancelled.

Inside `Main`, we create a task then await it inside a `try catch` block. However, when I instantiated the cancellation token source, I passed `3000` as an argument to it, which is a delay in milliseconds, which should simulate a process taking 3 seconds before getting cancelled.

If you run the code, you should see the following output:
![Output of the sample cancellation token code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3by6oz6pw4fprhxt6yw2.png)

## Real Use Cases of Cancellation Tokens ⚙️
Now with the sample counter program out of the way, let's investigate some real world use cases that highlight the benefit of cancellation tokens.

```csharp

[HttpGet("get-users")]
public async Task<IActionResult> GetUsersAsync(CancellationToken cancellationToken)
{
    try
    {
        var users = await UsersRepo.GetAllUsersAsync(cancellationToken);

        return Ok(users);
    }
    catch (OperationCanceledException ex)
    {
        return Ok("Cancelled loading users query");
    }
}

```

Starting off with this demo API endpoint, this endpoint is supposed to load all the users available in the database and return them.

> ⚠️ Please note that this is an incomplete implementation, in such a scenario, you need to handle other types of exceptions, add pagination, filtering and more.

In our endpoint parameters, we added a cancellation token parameter, now when you write a service using `HttpClient` to call this endpoint for instance, you can pass a cancellation token which as we said earlier, you can get from a cancellation token source object and maybe in the user interface relying on that http service, you could either add a cancel button, or check for when the user navigates from the page, if the task was still undone, you can call the `Cancel` method such that the task is cancelled properly.

## Cancellation Tokens With Entity Framework Core 🗂️
When using the `Async` variants of Entity Framework Core LINQ methods, you'll always find an overload accepting a cancellation token, and now since you learned how to use this powerful tool, you should always pass one when possible.

Let's investigate this piece of code:
```csharp

public class EventsManagementService
{
    private readonly ApplicationDbContext _dbContext;

    public EventsManagementService(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<List<LogStore>> GetEventsAsync(CancellationToken cancellationToken)
    {
        // Use AsNoTracking() to avoid change tracking overhead
        var events = await _dbContext.LogStore.AsNoTracking().ToListAsync(cancellationToken);
        return events;
    }
}

```

In this events management service class, we have a method that retrieves a list of `LogStore` objects, the method accepts a cancellation token, which is passed down to the `ToListAsync` method. This method of EF Core asynchronously creates a list from an `IQueryable` by enumerating the results from the query. This way, when cancellation is requested, this method will not continue to create the list thus saving our application from burned resources.

## Conclusion 
In this post, we've looked at the concept of task cancellation in general, and the way it works in C#. I didn't want to dive much in the theory, but instead show you actual code so that you see the concept in use. Now equipped with this new knowledge, you can smoothly obtain a cancellation token, set a cancellation delay, and actually cancel an async operation.
I hope you learned something new from this post!

## Thanks for Reading!