To cancel an async task in C#, signal cancellation with a CancellationTokenSource and pass its CancellationToken to the work. Cancellation is cooperative: the request does not forcibly kill a task, and work stops only when the operation observes the token and responds. The same pattern lets you stop a cancellable HttpClient request; if work keeps running after Cancel(), it may not be checking or forwarding the token.
How CancellationToken works
A CancellationTokenSource owns the cancellation request. Its Token is the signal that you pass to operations. Work observes the token; it does not use the token itself to request cancellation. Microsoft describes a token as an object that indicates whether cancellation has been requested (CancellationToken API reference).
As an Amazon Associate I earn from qualifying purchases.
Cancellation is cooperative. The caller signals; each listener decides how and when it can stop safely, often after finishing a small unit of work or performing cleanup. As Microsoft puts it, “The calling thread does not forcibly end the task; it only signals that cancellation is requested” (Task cancellation guidance). A request can therefore take time to take effect, or arrive after an operation has already completed.
Pass the token to the work that can stop
Pass the token through your method calls and into APIs that accept one. Checking only before starting an operation does not make an in-progress network read cancelable. Microsoft’s HTTP example forwards the token to both the request and the response-body read (Consuming the Task-based Asynchronous Pattern).
#1 Best Overall
static async Task ProcessAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await DoCancelableWorkAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
}
using var cts = new CancellationTokenSource();
Task work = ProcessAsync(cts.Token);
// When the result is no longer needed:
cts.Cancel();
try
{
await work;
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
// Perform caller-level cleanup if needed.
}
This is a usage pattern, not a guarantee that every operation will stop instantly. In production, prefer token-aware overloads for asynchronous I/O and pass the same token through layers that can honor it. For synchronous CPU work, check at meaningful boundaries. Do not catch and suppress cancellation indiscriminately; let it propagate unless that layer deliberately translates it.
CPU-bound loops
For a loop doing synchronous work, use IsCancellationRequested at useful boundaries when a graceful exit or cleanup is appropriate. If the task should report a canceled state, throw with ThrowIfCancellationRequested() rather than simply returning.
Rank #2
HTTP requests
Pass the token to each cancellable operation, including reading the response body:
using var cts = new CancellationTokenSource();
byte[] data = await httpClient.GetByteArrayAsync(url, cts.Token);
Here the token is supplied to a token-aware HTTP API, so cancellation can reach active request work. Forwarding a token only to the outer method while omitting it from the underlying API leaves that operation without the cancellation signal.
What task status should cancellation produce?
Stopping work and marking a task as canceled are related but different outcomes. A delegate that notices cancellation and returns may finish in RanToCompletion. To have a task enter Canceled, the usual pattern is to throw OperationCanceledException carrying the task’s associated token, with cancellation requested on that token. The task checks both the token match and its requested state; if those conditions are not met, the exception is treated as a fault. Other exceptions also fault the task (Task cancellation guidance).
| Operation’s response | Typical task outcome | Use it when |
|---|---|---|
| Return after noticing cancellation and completing cleanup | May be RanToCompletion |
Stopping is enough and callers do not need canceled task status. |
Throw OperationCanceledException with the requested associated token |
Canceled, when the token matches and is requested |
Callers should be able to observe cancellation as the task’s outcome. |
| Throw with a different or unrequested token, or throw another exception | Faulted | This is not the normal cancellation path; inspect the error rather than assuming cancellation. |
At an async boundary where cancellation is expected, handle OperationCanceledException (or its common derived type, TaskCanceledException) deliberately. Synchronous task-waiting APIs can surface cancellation wrapped in AggregateException; an await generally surfaces the cancellation exception directly. Avoid treating expected cancellation as a system failure, but do not hide unrelated faults.
Rank #4
Choose how cancellation is requested
| Mechanism | What it does | Best fit |
|---|---|---|
Cancel() |
Signals cancellation when the caller decides the result is no longer needed. | User action, abandoned work, or an explicit stop decision. |
CancelAfter(TimeSpan) |
Schedules a cancellation request after a time interval. | A deadline or local timeout. |
| Linked cancellation source | Creates a source that is canceled when any linked source token is canceled. | One operation must honor both caller cancellation and a local timeout. |
For example, combine a caller token with a local deadline without discarding either request:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
callerToken,
timeoutCts.Token);
await DoCancelableWorkAsync(linkedCts.Token);
The ten-second interval in this example is a chosen timeout, not a general .NET default. Dispose each source when its cancellation coordination is finished; CancellationTokenSource implements IDisposable (CancellationTokenSource API reference).
Best Value
When to use CancelAsync
CancelAsync() has different callback scheduling behavior from Cancel(). The token’s IsCancellationRequested state changes synchronously, while registered callbacks and cancelable operations run asynchronously; the returned task completes after they finish. Microsoft advises that registered callbacks should not throw, since thrown exceptions are aggregated. Check framework compatibility before using it: the .NET 10 API reference lists the method for .NET 8, 9, 10, and 11 (CancelAsync API reference).
Why cancellation may not save resources
- The token never reaches the work: forward it through methods and token-aware APIs, especially active I/O.
- The operation does not observe it: add checks at safe boundaries in synchronous work, or use cancellable asynchronous overloads.
- The operation is cleaning up: a listener may need to finish a safe stopping path before it returns.
- The work already finished: cancellation is a request, not a reversal of completed work.
- The task returned normally: work may have stopped while the task still reports
RanToCompletion.
Cancellation can avoid work that has not yet completed when listeners receive and honor the request. Its resource benefit depends on that cooperation; there is no fixed CPU, memory, network, or latency saving guaranteed by calling Cancel().
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Recommended Free Tools




