Imagine you’re cooking dinner, and halfway through, you realize you’re out of an important ingredient. You’d want to stop cooking and run to the store, right? Well, in the coding world, CancellationToken is like that “stop cooking” signal. It’s a way to tell your code, “Hey, something’s come up, let’s pause this operation.”

In more technical terms, CancellationToken is a struct used to signal cancellation in multi-threaded applications. It’s like a red flag you can wave when you need a task to halt.

Understanding CancellationTokenSource

CancellationTokenSource is a class that provides a way to create and manage cancellation tokens. These tokens are used to signal and handle cancellation requests in a multi-threaded or asynchronous environment.

  1. Creating a CancellationTokenSource:
    • You instantiate a CancellationTokenSource to create a new source of cancellation tokens.
    • Example: var cts = new CancellationTokenSource();
  2. Generating a CancellationToken:
    • From a CancellationTokenSource, you generate a CancellationToken which is used by tasks or operations to monitor for cancellation requests.
    • Example: CancellationToken token = cts.Token;
  3. Signaling Cancellation:
    • When you want to request cancellation, you call the Cancel method on the CancellationTokenSource.
    • Example: cts.Cancel();

Properties and Methods

Here’s a breakdown of some important properties and methods of CancellationTokenSource:

  1. Properties:
    • Token: Gets the CancellationToken associated with this CancellationTokenSource.
    • IsCancellationRequested: Indicates whether cancellation has been requested for this CancellationTokenSource.
  2. Methods:
    • Cancel(): Signals cancellation to all tasks or operations that are observing the CancellationToken.
    • CancelAfter(TimeSpan delay): Schedules a cancellation request to be sent after the specified time delay.
    • Dispose() : Releases resources used by CancellationTokenSource.
    • TryReset(): Reset the CancellationTokenSource so it can be reused for a new, unrelated operation. WARNING!!! This does not work when the cancellation has already happened.

Let’s look at a few examples to see how CancellationTokenSource can be used in different scenarios:

public void Example()
{
    try
    {
        var cts = new CancellationTokenSource();
        var token = cts.Token;

        Task.Run(() =>
        {
            while (!token.IsCancellationRequested)
            {
                Console.WriteLine("Running...");
                Thread.Sleep(1000);
            }
        });

        Console.WriteLine("Press any key to cancel the operation");
        Console.ReadKey();
        cts.Cancel();
    }
    catch (OperationCanceledException ex)
    {
        Console.WriteLine($"The operation was cancelled. {ex}");
    }
}

In the example above, when a button is pressed, the task will stop running.

public async Task TimeoutExample()
{
    using var cts = new CancellationTokenSource();
    var token = cts.Token;

    cts.CancelAfter(TimeSpan.FromSeconds(5));

    var task = Task.Run(() => DoWork(token), token);

    try
    {
        await task;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Task was cancelled due to timeout.");
    }

    static void DoWork(CancellationToken token)
    {
        for (int i = 0; i < 10; i++)
        {
            token.ThrowIfCancellationRequested();
            Console.WriteLine($"Working... {i}");
            Thread.Sleep(1000); // Simulate work
        }
    }
}

In this example:

  • We use cts.CancelAfter(TimeSpan.FromSeconds(5)) to automatically cancel the task after 5 seconds.
  • The task respects this cancellation request and exits after the specified timeout.
public async Task LinkTokensExample()
{
    using var cts1 = new CancellationTokenSource();
    using var cts2 = new CancellationTokenSource();
    using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);

    var token = linkedCts.Token;

    var task = Task.Run(() => DoWork(token), token);

    // Simulate user canceling from either source
    await Task.Delay(2000);
    cts1.Cancel();

    try
    {
        await task;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Task was cancelled from one of the sources.");
    }
}

static void DoWork(CancellationToken token)
{
    for (int i = 0; i < 10; i++)
    {
        token.ThrowIfCancellationRequested();
        Console.WriteLine($"Working... {i}");
        Thread.Sleep(1000); // Simulate work
    }
}

Sometimes, you might need to handle multiple cancellation sources. You can combine them using the method CancellationTokenSource.CreateLinkedTokenSource().

In this example:

  • We create two CancellationTokenSource instances.
  • We combine them using CancellationTokenSource.CreateLinkedTokenSource().
  • Cancelling any of the original sources (cts1 or cts2) will cancel the task.
app.MapGet("/fetchdata", async (CancellationToken cancellationToken) =>
{
    try
    {
        var data = await FetchDataAsync(cancellationToken);
        return Results.Ok(data);
    }
    catch (OperationCanceledException)
    {
        return Results.Problem("The request was cancelled.");
    }
});

async Task<string> FetchDataAsync(CancellationToken cancellationToken)
{
    for (int i = 0; i < 10; i++)
    {
        cancellationToken.ThrowIfCancellationRequested();
        await Task.Delay(1000, cancellationToken); // Simulate work
    }
    return "Fetched data successfully!";
}

In the example above:

  1. API Endpoint: The endpoint /fetchdata takes a CancellationToken as a parameter. This token is passed by the ASP.NET Core runtime and is tied to the HTTP request lifecycle.
    • When a client cancels the HTTP request (e.g., by closing the browser tab), the runtime signals the CancellationToken associated with that request.
  2. Task Execution: Inside the FetchDataAsync method, the token is periodically checked using cancellationToken.ThrowIfCancellationRequested().
    • If the client cancels the request, the token is signaled, and ThrowIfCancellationRequested throws an OperationCanceledException.
  3. Handling Cancellation: The exception is caught in the endpoint, and an appropriate response is returned to indicate that the task was canceled.

If you are using EF Core, you can pass the token to the DBContext and EF Core will handle the cancellation for you.

static async Task FetchProductsAsync(CancellationToken token)
{
    using var context = new ApplicationDbContext();

    var products = await context.Products
        .Where(p => p.Price > 10)
        .ToListAsync(token); // Pass the CancellationToken here

    foreach (var product in products)
    {
        Console.WriteLine($"Product: {product.Name}, Price: {product.Price}");
    }
}

Why Should You Care?

Using CancellationToken and CancellationTokenSource makes your applications more responsive and user-friendly. Users can cancel operations that are taking too long, and your app can gracefully handle these interruptions without crashing or freezing. Imagine a malicious user causing multiple long running queries and cancelling them, would your application handle it?

Wrapping Up

CancellationTokenSource is a powerful tool for managing cancellations in C#. It allows you to create, signal, and handle cancellation requests gracefully.

And there you have it! A quick introduction to CancellationToken and CancellationTokenSource in C#. These tools are essential for writing responsive and user-friendly applications. So next time you’re working on a long-running task or an API give these a try and see how they make your life easier.

Affiliate promo

If you love learning new stuff and want to support me, consider buying a course from Dometrain using this link: Browse courses – Dometrain. Thank you!

Leave a comment

Trending