Let's say a user begins to download a sizable report from your ASP.NET Core API. They either terminate the request or close the browser after a few seconds. The server may continue be querying the database, contacting another API, or processing data even after the client is no longer waiting for the outcome.
If this occurs frequently, your program may waste network resources, database connections, and CPU time on tasks that are no longer necessary.
This is where CancellationToken becomes useful. It allows an operation to receive a cancellation request and stop when it is safe to do so.
However, adding a cancellation token to a method does not automatically stop every operation inside it. Your code must pass the token to supported APIs and handle cancellation correctly.
In this article, we will learn how to use CancellationToken in ASP.NET Core, propagate cancellation through database and HTTP calls, and avoid common mistakes in production applications.
What Is CancellationToken in C#?
CancellationToken is a .NET type that allows one part of an application to notify another part that an operation should stop.
It does not forcibly terminate a running method. Instead, it provides a cooperative cancellation mechanism. The operation must check the token or call an API that observes it.
Consider this example:
public async Task ProcessAsync(
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(
TimeSpan.FromSeconds(10),
cancellationToken);
Console.WriteLine("Processing completed.");
}If cancellation is requested while Task.Delay is running, the delay can stop early by throwing an OperationCanceledException.
The important point is that the token must reach the operation that can actually respond to cancellation.
How ASP.NET Core Handles Request Cancellation
ASP.NET Core exposes HttpContext.RequestAborted, a cancellation token that indicates when the current HTTP request has been aborted.
In controller actions, ASP.NET Core can also bind a CancellationToken parameter to the request-aborted token.
For example:
[HttpGet("report")]
public async Task<IActionResult> GetReport(
CancellationToken cancellationToken)
{
var report = await GenerateReportAsync(
cancellationToken);
return Ok(report);
}When the request is aborted, the token can signal that the operation should stop.
This is particularly useful for long-running database queries, external HTTP requests, file operations, and data processing.
However, cancellation is not guaranteed to happen immediately. Some operations may already have completed, and some dependencies may not support cancellation at every stage.
Step 1: Pass CancellationToken to Entity Framework Core
Database operations can consume resources even after a client stops waiting for the result.
Entity Framework Core provides asynchronous methods that accept a cancellation token, including ToListAsync, FirstOrDefaultAsync, and SaveChangesAsync.
Consider this example:
[HttpGet("orders")]
public async Task<IActionResult> GetOrders(
CancellationToken cancellationToken)
{
var orders = await _context.Orders
.AsNoTracking()
.OrderByDescending(o => o.CreatedAt)
.Take(100)
.Select(o => new
{
o.Id,
o.CreatedAt,
o.TotalAmount
})
.ToListAsync(cancellationToken);
return Ok(orders);
}The cancellation token is passed to ToListAsync. If the request is cancelled, EF Core can pass the cancellation request to the database provider.
Whether the underlying database operation stops immediately depends on the provider and database driver.
Without the token, the query may continue running even when the request is no longer useful. The exact behavior depends on the operation and provider, so test cancellation with your actual database setup.
Step 2: Cancel External HTTP Requests
ASP.NET Core applications often call external services to retrieve prices, customer details, payment status, or other information.
Use HttpClient methods that accept a cancellation token so an abandoned request does not unnecessarily wait for the external service.
public sealed class PricingService
{
private readonly HttpClient _httpClient;
public PricingService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<string> GetPricingAsync(
CancellationToken cancellationToken)
{
using var response = await _httpClient.GetAsync(
"api/pricing",
cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(
cancellationToken);
}
}Register the service with IHttpClientFactory:
builder.Services.AddHttpClient<PricingService>(client =>
{
client.BaseAddress =
new Uri("https://pricing.example.com/");
client.Timeout = TimeSpan.FromSeconds(15);
});The timeout is an additional safeguard. It does not replace request cancellation.
The external service may still have received and started processing the request before cancellation reaches it. Cancellation primarily helps your application stop waiting and release resources when the underlying operation supports it.
For critical operations, such as a payment request, do not assume that cancelling the HTTP request reverses a transaction that the external service has already completed.
Step 3: Pass Cancellation Through Every Layer
A common mistake is accepting a cancellation token in the controller but not passing it to the service or repository.
Consider a service method:
public async Task<List<Product>> GetProductsAsync(
CancellationToken cancellationToken)
{
return await _context.Products
.AsNoTracking()
.ToListAsync(cancellationToken);
}The controller should pass its token to the service:
[HttpGet("products")]
public async Task<IActionResult> GetProducts(
CancellationToken cancellationToken)
{
var products = await _productService
.GetProductsAsync(cancellationToken);
return Ok(products);
}This keeps cancellation flowing from the incoming request to the database operation.
The same principle applies to repository methods, file processing, HTTP calls, and other asynchronous operations.
If an intermediate method ignores the token, work inside that method may continue even after the request is cancelled.
Step 4: Handle Cancellation Without Hiding Errors
Cancellation is an expected outcome in many web applications. A user may navigate away, close a browser tab, or stop an operation.
You should handle cancellation differently from unexpected application errors.
For example:
[HttpGet("export")]
public async Task<IActionResult> Export(
CancellationToken cancellationToken)
{
try
{
var file = await _exportService.CreateAsync(
cancellationToken);
return File(
file,
"application/octet-stream",
"export.bin");
}
catch (OperationCanceledException)
when (cancellationToken.IsCancellationRequested)
{
// The request was cancelled.
throw;
}
}This example rethrows cancellation rather than treating it as a successful export. In many controllers, no explicit catch is needed at all; allowing cancellation to propagate is simpler.
Avoid catching every exception and returning 200 OK. Doing so can hide genuine failures and make monitoring unreliable.
Also, do not assume every OperationCanceledException means the client disconnected. A linked token or an internal timeout may have caused the cancellation. Check the relevant token and handle each case according to the application's requirements.
Step 5: Combine Request Cancellation with an Operation Timeout
Sometimes an operation should stop not only when the client disconnects but also when it exceeds a time limit.
You can combine cancellation sources using a linked token.
public async Task<Result> RunOperationAsync(
CancellationToken requestToken)
{
using var timeoutSource =
new CancellationTokenSource(
TimeSpan.FromSeconds(20));
using var linkedSource =
CancellationTokenSource.CreateLinkedTokenSource(
requestToken,
timeoutSource.Token);
return await PerformOperationAsync(
linkedSource.Token);
}The operation stops when either the request token or the timeout token is cancelled, provided the underlying work supports cancellation.
Keep the distinction between these cases. A client disconnect may be normal, while an operation timeout may indicate a slow dependency or an overloaded system.
CancellationToken vs. Timeout
Feature | CancellationToken | Timeout |
|---|---|---|
Main purpose | Signals that an operation should stop | Limits how long an operation may run |
Trigger | Cancellation requested by a caller or another source | A configured duration expires |
Cooperation required | Yes, the operation must observe it | Depends on how the timeout is implemented |
Common use | HTTP request cancellation | External API or database operation limits |
Can be combined | Yes | Yes, using a cancellation source or supported API timeout |
A timeout can be implemented through a cancellation token, but the concepts are not identical. Cancellation describes a request to stop; a timeout is one possible reason for that request.
Common Mistakes to Avoid
Accepting a Token but Never Using It
Adding a CancellationToken parameter has little value if the code never checks it or passes it to an operation that supports cancellation.
Using Task.Run for Every Async Operation
Wrapping database or HTTP calls in Task.Run does not automatically make them cancellable. Use the asynchronous APIs provided by the underlying libraries and pass the token directly.
Swallowing OperationCanceledException
Catching cancellation and returning a successful result can make the application report work as completed when it was actually interrupted.
Assuming Cancellation Rolls Back Completed Work
Cancellation does not automatically undo a database update, payment, or external side effect that has already completed. Use transactions, idempotency, or explicit compensation where appropriate.
Forgetting Background Jobs
An HTTP request token is intended to represent the lifetime of that request. If work must continue after the request ends, such as generating a report in the background, use a durable job queue or hosted background service with its own cancellation and shutdown strategy.
Best Practices for Production Applications
Accept
CancellationTokenin asynchronous methods that perform cancellable work.Pass the token through controller, service, repository, and dependency layers.
Use asynchronous database and HTTP APIs with cancellation support.
Set realistic timeouts for external dependencies.
Distinguish client cancellation from timeouts and unexpected exceptions.
Avoid starting background work that depends on the lifetime of an HTTP request.
Log cancellation carefully without treating every cancelled request as an application failure.
Test cancellation with slow database queries, external HTTP calls, and long-running operations.
Do not rely on cancellation to reverse completed transactions or external side effects.
Ensure that application shutdown and background processing have their own cancellation policies.
Advantages and Disadvantages
Advantages
Reduces unnecessary work when requests are abandoned.
Can release database and network resources sooner.
Helps limit the duration of long-running operations.
Supports cleaner shutdown and cancellation flows.
Works across many asynchronous .NET APIs.
Disadvantages
Requires cooperation from the operations being cancelled.
Cancellation may not stop work immediately.
Can complicate error handling if different cancellation sources are mixed together.
Does not automatically undo completed side effects.
Some third-party libraries and external services may not fully support cancellation.
Summary
CancellationToken is an important tool for building responsive ASP.NET Core applications. It allows request cancellation to flow through database queries, HTTP calls, and other asynchronous operations so the application can stop unnecessary work when possible.
The most important practice is to propagate the token through every layer and pass it to APIs that support cancellation. Handle cancellation separately from unexpected errors, and remember that stopping an operation does not automatically undo work that has already completed.
Used correctly, cancellation helps your application use resources more responsibly, especially when handling slow requests and long-running operations.
Best ASP.NET Core 11.0 Hosting Recommendation
At HostForLIFE, customers can also experience fast ASP.NET Core 11 hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFE guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.









