Should I always add CancellationToken to my controller actions?

Is this a good practice to always add CancellationToken in my actions no matter if operation is long or not?

I'm currently adding it to every action and I don't know if it's right or wrong.

[ApiController]
[Route("api/[controller]")]
public class DummiesController : ControllerBase
{
    private readonly AppDbContext _dbContext;

    public DummyController(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Dummy>> GetAsync(int id, CancellationToken ct) // <<----- should I always do this?
    {
        var dummy = await _dbContext.Dummies.AsNoTracking().SingleOrDefaultAsync(e=>e.Id == id, ct);
        if (dummy == null) return NotFound();
        return dummy;
    }
}

Also is adding CancellationToken ct = default(CancellationToken) necessary?


Solution 1:

Should I always add CancellationToken to my controller actions?

No. You should not always.

Using CancellationTokens in ASP.NET Core MVC controllers
https://andrewlock.net/using-cancellationtokens-in-asp-net-core-mvc-controllers/

Whether this is correct behaviour will depend on your app. If the request modifies state, then you may not want to halt execution mid-way through a method. On the other hand, if the request has no side-effects, then you probably want to stop the (presumably expensive) action as soon as you can.

So if you have a method/action like below (simplified);

await ProcessOrder();
await UpdateInventory();

You do not want to cancel the order while it is being processed, if so, order can be completed, but you will not update the inventory if user passing through the tunnel, and loses the internet connection.

This is especially important when operations cannot be included in a Unit of Work like pattern (e.g. distributed system) and one should try to minimize cancellations.

Solution 2:

It's worthwhile adding if you have any dependency on an external resource.

Let's say your database is busy, or you have transient error handling / retry policies in place for your database connection. If an end user presses stop in their browser the cancellation token will aid any waiting operations (in your code) to cancel gracefully.

Think of it less about the long running nature under normal circumstances, but long running nature in less than ideal circumstances e.g. if running in Azure and your SQL database is undergoing routine maintenance and entity framework is configured to retry itself (asuming Entity framework responds to cancellation tokens sensibly).

As a side note: Polly resilience framework has excellent support for external cancellation tokens to coordinate retry cancellations from an external cancellation. Their wiki is worthwhile a read as it's a great eye opener to coordinating cancellation: https://github.com/App-vNext/Polly/wiki

Regarding CancellationToken ct = default(CancellationToken), it's probably more worthwhile on library code than on a Controller action. But handy if you are unit testing your controller directly but don't want to pass in a cancellation token. Having said that .net core's WebHostBuilder testframework is easier to test against than testing controller actions directly these days.

Solution 3:

I think cancellation token should be used mostly for query type operation. Take CRUD (Create, Read, Update, Delete) operation as example, break it down based on CQRS (Command Query Responsibility Segregation).

  • Command: Create, Update, Delete
  • Query: Read

As you can see, for Query operation, it will not be a problem to cancel at any time. But for Command, you most probably don't want to cancel any of the operation. For example Create, imagine you are creating data for 2 separate databases, then got cancelled half way, it'll cause unsynchronized data.