top of page

How to use cancellation tokens?

A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects. You create a cancellation token by instantiating a CancellationTokenSource object, which manages cancellation tokens retrieved from its CancellationTokenSource.Token property. You then pass the cancellation token to any number of threads, tasks, or operations that should receive notice of cancellation. The token cannot be used to initiate cancellation. When the owning object calls CancellationTokenSource.Cancel, the IsCancellationRequested property on every copy of the cancellation token is set to true. The objects that receive the notification can respond in whatever manner is appropriate.


Web API implementation

Let’s start creating a new Web API project and adding the “MediatR” library. If you are not familiarized with this library you can check out my previous articles where I implemented the CQRS pattern with the library.


Add the following handler:

public class LongTaskRequest : IRequest
{
    public LongTaskRequest(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

public class LongTaskHandler : IRequestHandler<LongTaskRequest>
{
    /// <inheritdoc />
    public async Task<Unit> Handle(LongTaskRequest request, CancellationToken cancellationToken)
    {
        await Task.Delay(80000, cancellationToken);
        Console.WriteLine($"Name received: {request.Name}.");

        return Unit.Value;
    }
}

In this case, we are simulating a long task operation with “await Task.Delay(80000, cancellationToken)” and passing it the cancellation token, after that, we are just printing in the console a message.


Then, add the following controller:

[Route("[controller]")]
[ApiController]
public class LongTaskController : ControllerBase
{
    private readonly IMediator _mediator;

    public LongTaskController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet]
    public async Task<ActionResult> Index([FromQuery] string name, CancellationToken token)
    {
        await _mediator.Send(new LongTaskRequest(name), token);

        return Ok();
    }
}

We are exposing an endpoint, which receives a “name” as a parameter and the cancellation token. The body of the method is quite simple, we are calling the handler with the parameter received and the token.


Let’s execute our API and try to cancel it before it finishes.



As you can see, we can cancel the request pretty easily with Postman. Once we cancel the task, if we go to the output console, we will see the following exception:



This is because the task which it is being canceled, handles the cancellation in this way, not all libraries work in the same way. In order to solve this, we can just add a try/catch block like this:

[HttpGet]
public async Task<ActionResult> Index([FromQuery] string name, CancellationToken token)
{
    try
    {
        await _mediator.Send(new LongTaskRequest(name), token);
    }
    catch (TaskCanceledException)
    {
        Console.WriteLine("Task canceled.");
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Operation canceled.");
    }
    
    return Ok();
}

Note: instead, we can create a middleware to handle this kind of exception.


And, as you can see, we have the following result:



Console App

If you are working with a console app and you want to allow cancellations, is quite similar to the API but instead of using the “CancellationToken” directly, we need to use the “CancellationTokenSource” class.

This class will give us access to the token itself and we have a couple of ways to check if the task was canceled.

  • With a loop

private static async Task WithLoop(CancellationTokenSource cancellationTokenSource)
{
    while (!cancellationTokenSource.IsCancellationRequested)
    {
        Console.WriteLine("Task running.");
        await Task.Delay(2000);
    }
    
    Console.WriteLine("The operation was canceled.");
    
    cancellationTokenSource.Dispose();
}

How does this example work? well, pretty straightforward, the message “Task running.” will be displayed every 2 seconds until the user press “c”, at that moment the task will be canceled and the message “Task canceled.” will be displayed. After 2 seconds, in the console we will see “The operation was canceled.” and after that, the cancelation token source is being disposed.


  • Immediately

In this case, the code is almost the same except that we are going to pass the token to the “Task.Delay” method, this will prevent continuing with execution of the code.

while (!cancellationTokenSource.IsCancellationRequested)
{
    Console.WriteLine("Task running.");
    await Task.Delay(2000, cancellationTokenSource.Token);
}


As you can see, an exception is being raised and the message: “The operation was canceled.”, is not being displayed.

  • Throw exception

private static async Task WithException(CancellationTokenSource cancellationTokenSource)
{
    try
    {
        while (true)
        {
          cancellationTokenSource.Token.ThrowIfCancellationRequested();
            Console.WriteLine("Task running.");
            await Task.Delay(2000);
        }
    }
    catch (Exception e)
    {   
        Console.WriteLine(e);
    }
}

In this scenario, the only difference is we are throwing an exception when the task is canceled. In the example, I’m adding a try/catch block in order to see the exception, and as we can see it is an “OperationCanceledException”.



Source: Medium- Matias Paulo


The Tech Platform

0 comments
bottom of page