ASP.NET Core  

Cancellation Tokens in ASP.NET Core: Graceful Long-Running Operations

Mastering Cancellation Tokens in ASP.NET Core

Gracefully handle client disconnects and timeouts

What is a Cancellation Token?

A CancellationToken in ASP.NET Core is a lightweight, cooperative mechanism that enables you to signal cancellation requests to asynchronous operations. It acts as a notification system rather than forcing cancellation—your code must actively check the token and respond appropriately.

Think of it like a "stop" button for long-running tasks. When a client disconnects from your API or a timeout occurs, ASP.NET Core automatically creates a CancellationToken via HttpContext.RequestAborted. Your methods can monitor this token to exit early, preventing wasted server resources.

  
    public async Task ProcessAsync(CancellationToken cancellationToken)
{
    // Your code checks the token periodically
    cancellationToken.ThrowIfCancellationRequested();
}
  

Pros, Cons, and When to Use Cancellation Tokens

Pros:

  • Resource efficiency: Stops unnecessary work when clients disconnect

  • Scalability: Frees up threads and memory during high load

  • Built-in support: Works seamlessly with Task.Delay(), HttpClient, database queries

  • Predictable behavior: Graceful shutdowns instead of abrupt failures

Cons:

  • Cooperative nature: Requires explicit checks in your code—easy to forget

  • Loop complexity: Adds ThrowIfCancellationRequested() calls everywhere

  • Debugging overhead: Cancellation logic can complicate stack traces

  • Not for CPU-bound work: Doesn't interrupt synchronous operations effectively

When to use:

  • Long-running async APIs (file uploads, report generation)

  • External service calls (HTTP, databases with timeouts)

  • Background processing that should respect client lifecycle

When NOT to use:

  • Short, fast operations (< 100ms)

  • Pure CPU-bound synchronous work

  • Fire-and-forget scenarios where cancellation doesn't matter

Real-World Scenario: Long-Processing API

Consider a reporting API that generates complex data over several seconds. Clients might close their browser mid-process, but your server keeps running unnecessarily.

API Controller:

  
    [HttpGet("long-process")]
public async Task<IActionResult> LongProcess(CancellationToken cancellationToken)
{
    // Use HttpContext.RequestAborted for automatic client disconnect detection
    var result = await _getDataService.ProcessAsync(cancellationToken);
    
    return result == "Done" 
        ? Ok("Process completed") 
        : StatusCode(500, "Process failed");
}
  

Service Implementation:

  
    public class GetDataService
{
    public async Task<string> ProcessAsync(CancellationToken cancellationToken)
    {
        for (int i = 0; i < 5; i++)
        {
            // Check for cancellation at each step
            cancellationToken.ThrowIfCancellationRequested();
            
            // Simulate work (respects cancellation)
            await Task.Delay(2000, cancellationToken);
            
            // Log progress (in real apps)
            Console.WriteLine($"Step {i + 1}/5 completed");
        }
        return "Done";
    }
}
  

What happens:

  1. Client calls /api/check/long-process

  2. Each 2-second delay checks if client disconnected

  3. If client closes browser → ThrowIfCancellationRequested() throws OperationCanceledException

  4. API returns TaskCanceledException (handled as 499 by most gateways)

Best Practices for Production

✅ DO: Pass CancellationToken through all layers

✅ DO: Check token before/after expensive async operations

✅ DO: Use with HttpClient.Timeout and DB timeouts

❌ DON'T: Catch OperationCanceledException and treat as error

❌ DON'T: Forget to pass cancellationToken to Task.Delay()

❌ DON'T: Use for synchronous CPU work

Conclusion

Cancellation Tokens transform how you handle long-running operations in ASP.NET Core. They provide a clean, standardized way to respect client intent while protecting server resources. The key is consistency—pass tokens everywhere and check them religiously.

Implement this pattern in your next long-running API, and you'll immediately notice improved scalability and resource utilization.

Source Code

You can find the complete working demo here:

GitHub Repository: JwtCancellationTokenDemo

  
    src/Controllers/CheckController.cs | src/Services/GetDataService.cs