SALE! All courses are on the lowest price! Enroll Now
C# Best Practices

Mastering Cancellation Tokens in .NET: The Complete Guide

Bhrugen Patel

Bhrugen Patel

Author

Published
December 17, 2025
15 min read
57 views
Learn how to properly implement cancellation tokens in .NET to build responsive, resource-efficient applications. Discover best practices, common pitfalls, and real-world examples of cooperative cancellation.
RESTful Web API in .NET Core - The Advanced Guide (.NET 10)

RESTful Web API in .NET Core - The Advanced Guide (.NET 10)

Intermediate course on RESTful API with ASP.NET Core Web API that will dive into concepts like file uploads, refresh token, exception handling and more!

61 Videos 5 hr
Share this article:

CancellationToken in ASP.NET Core APIs: Stop Wasting Server Resources

Here's a scenario: A user makes a request to your API, it starts processing, then the user gets impatient and closes their browser. Your API? Still chugging away, doing work that nobody will ever see. Database queries running, external APIs being called, all for nothing.

That's where CancellationToken comes in. It's your way of saying ""hey, if the client doesn't care anymore, let's stop wasting resources.""

What Even Is a CancellationToken?

Think of CancellationToken as a messenger that tells your code ""the client disconnected"" or ""this operation is taking too long, give up."" When that happens, you can bail out early instead of finishing work nobody wants.

💡 The Magic Part

ASP.NET Core automatically creates and passes a CancellationToken to your controller actions. When the HTTP request is aborted (user closes browser, timeout, etc.), that token gets cancelled. You just need to use it!

The Simplest Example

Here's what most people do (the wrong way):

[HttpGet(""products"")]
public async Task<IActionResult> GetProducts()
{
    // This will keep running even if the client disconnects
    var products = await _context.Products.ToListAsync();
    return Ok(products);
}

Now here's the better way:

[HttpGet(""products"")]
public async Task<IActionResult> GetProducts(CancellationToken cancellationToken)
{
    // If the client disconnects, this stops automatically
    var products = await _context.Products.ToListAsync(cancellationToken);
    return Ok(products);
}

That's it! Just add the parameter and pass it along. ASP.NET Core handles the rest.

A Real-World Example: Report Generation

Let's say you're building a report generation endpoint. It takes 5 seconds to generate a sales report. A user clicks ""Generate Report"", waits 2 seconds, gets impatient, and closes the browser tab.

❌ Without CancellationToken (The Problem)

[HttpGet(""reports/sales"")]
public async Task<IActionResult> GenerateSalesReport()
{
    // Simulate fetching data from database (2 seconds)
    await Task.Delay(2000);
    var salesData = await _context.Sales.ToListAsync();
    
    // Simulate processing and generating report (3 seconds)
    await Task.Delay(3000);
    var report = GenerateReport(salesData);
    
    return Ok(report);
}

🔥 The Problem

  • User closes browser after 2 seconds
  • Your server keeps running for another 3 seconds
  • Database connections stay open unnecessarily
  • Server resources wasted on work nobody will see
  • Multiply this by 100 impatient users = serious resource drain!

✅ With CancellationToken (The Solution)

[HttpGet(""reports/sales"")]
public async Task<IActionResult> GenerateSalesReport(CancellationToken cancellationToken)
{
    try
    {
        // Simulate fetching data from database (2 seconds)
        await Task.Delay(2000, cancellationToken);
        var salesData = await _context.Sales.ToListAsync(cancellationToken);
        
        // Simulate processing and generating report (3 seconds)
        await Task.Delay(3000, cancellationToken);
        var report = GenerateReport(salesData);
        
        return Ok(report);
    }
    catch (OperationCanceledException)
    {
        // Client disconnected - log it and move on
        _logger.LogInformation(""Report generation cancelled by client"");
        return StatusCode(499, ""Client closed request"");
    }
}

✨ What Changed?

  • User closes browser after 2 seconds
  • CancellationToken gets triggered immediately
  • Task.Delay throws OperationCanceledException
  • Processing stops instantly - no wasted 3 seconds!
  • Database connections released immediately
  • Server resources freed up for actual users

💡 Real Impact

Imagine this scenario at scale:

Without CancellationToken:

100 impatient users × 5 seconds wasted = 500 seconds of wasted server time

With CancellationToken:

Operations stop immediately when cancelled = Minimal resource waste

Passing It Through Your Service Layer

Don't just use it in controllers! Pass it through your entire call chain:

// Controller
[HttpGet(""orders/{id}"")]
public async Task<IActionResult> GetOrder(
    int id, 
    CancellationToken cancellationToken)
{
    var order = await _orderService.GetOrderDetailsAsync(id, cancellationToken);
    return Ok(order);
}

// Service
public class OrderService
{
    public async Task<OrderDto> GetOrderDetailsAsync(
        int orderId, 
        CancellationToken cancellationToken)
    {
        var order = await _context.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);

        // Call another service
        var shipping = await _shippingService
            .GetShippingInfoAsync(order.ShippingId, cancellationToken);

        return MapToDto(order, shipping);
    }
}

// External API call
public class ShippingService
{
    private readonly HttpClient _httpClient;

    public async Task<ShippingInfo> GetShippingInfoAsync(
        int shippingId, 
        CancellationToken cancellationToken)
    {
        var response = await _httpClient.GetAsync(
            $""https://api.shipping.com/info/{shippingId}"", 
            cancellationToken); // HttpClient supports it too!

        return await response.Content
            .ReadFromJsonAsync<ShippingInfo>(cancellationToken);
    }
}

💡 Notice how the CancellationToken flows through every layer? That's the pattern. Don't stop at the controller!

Best Practices

1. Always add CancellationToken to async methods

Make it the last parameter. It's optional by default (uses CancellationToken.None).

2. Pass it through your entire call stack

Controller → Service → Repository → Database. Every layer should respect cancellation.

3. Don't swallow OperationCanceledException

Either let it bubble up or handle it specifically. Don't catch it as a generic Exception and ignore it.

4. Check cancellation in long-running loops

If you're processing thousands of items, check every N iterations at minimum.

5. Remember HttpClient already supports it

Most .NET libraries do! Always look for overloads that accept CancellationToken.

When NOT to Use CancellationToken

🚫 Don't use it for:

  • Critical operations that MUST complete (payments, data commits)
  • Fire-and-forget background tasks that run independently
  • Operations where partial completion is dangerous (like partial database writes)

If cancelling an operation would leave your system in an inconsistent state, you probably shouldn't use CancellationToken there!

The Bottom Line

CancellationToken is one of those features that seems like extra work at first, but it's actually super easy once you get into the habit. Just add it as a parameter and pass it along. Your server will thank you for not wasting resources on abandoned requests.

⚡ Quick Recap:

  • Add CancellationToken as the last parameter in your API methods
  • ASP.NET Core automatically cancels it when the client disconnects
  • Pass it through your entire call stack (controllers → services → repositories)
  • Most .NET async methods support it out of the box
  • Catch OperationCanceledException to handle cancellation gracefully
  • Don't use it for critical operations that must complete

Start adding it today, your API will be more efficient and your server less stressed!

Bhrugen Patel

About Bhrugen Patel

Expert .NET developer and educator with years of experience in building real-world applications and teaching developers around the world.

FREE .NET Hosting
YouTube
Subscribe