Cancellation is a cooperative request
A CancellationTokenSource owns the authority to request cancellation, while a CancellationToken lets an operation observe that request. Passing the token is not enough unless the implementation checks it or supplies it to an API that honors cancellation. Cooperative cancellation means the operation participates in stopping at defined boundaries. It is not a universal instruction that forcibly interrupts arbitrary code or reverses effects that already occurred. The sample isolates this contract using a request that exists before the operation begins.
View full size ↗Read the diagram
An owner calls Cancel on a CancellationTokenSource. Its tokens now report that cancellation has been requested.
A participating operation checks the token or passes it to a token-aware API. ThrowIfCancellationRequested throws OperationCanceledException when the request is present.
An async operation that propagates the cancellation exception completes its task as canceled; an awaiting caller can handle OperationCanceledException. Code that catches and suppresses it may instead complete successfully.
Cancel does not forcibly terminate a thread. An operation that ignores the token can continue. A completed operation is not retroactively undone by a later cancellation request.
Illustration created with ImageGen for SharpRune.
The early guard prevents the successful result
The source is created and immediately canceled. ReadAsync receives its token and calls ThrowIfCancellationRequested before the timer. That guard observes the existing request and prevents the method from returning seven. Awaiting the operation propagates cancellation into the caller’s catch, which prints exactly cancelled. The WriteLine around the awaited successful result never prints a number on this path. Because the token is canceled in advance, the outcome does not depend on a race involving the one-millisecond timer.
token.ThrowIfCancellationRequested();Forward the same lifetime through the operation
ReadAsync also passes the token into Task.Delay. If cancellation were requested during that wait rather than before the initial guard, the token-aware delay would provide another observation point. This illustrates a general rule for layered APIs: forward the owning token through helpers and cancelable awaited calls. A token parameter that is accepted and then ignored creates a misleading signature. Keep the source with its lifetime owner and give operations the observation token instead of granting every helper permission to cancel unrelated sibling work.
Disposal, stopping a wait, and stopping work differ
The using declaration disposes the source when its scope ends; disposal is not the same action as source.Cancel. Similarly, a caller can stop waiting for an operation without making the operation itself stop. This lesson demonstrates an operation that actually observes a token, not merely a wrapper ending its wait. A host forcibly ending execution also lies outside this ordinary cooperative flow. Do not infer from the sample that cancellation rolls back prior changes or guarantees cleanup after arbitrary process termination.
Practice: compare canceled and uncanceled input lifetimes
Remove only the source.Cancel call and predict that the successful result seven is printed instead of the cancellation label. Restore it and add a marker immediately before the guard and another after the guard. Predict which markers can appear for an already-canceled token. This local variation shows where the implementation observes the request. Keep cancellation before the method call so the expected result remains deterministic rather than depending on how quickly a small timer happens to complete.
- Keep the spelling
cancelledwhen comparing the original sample’s expected output. - Distinguish an expected OperationCanceledException from an unrelated fault rather than reporting every exception as user cancellation.
Practice: add a helper layer without losing the token
For an independent extension, move the delay into an inner helper that accepts a CancellationToken, and forward the outer method’s token to it. Test an already-canceled token with and without the explicit initial guard, relying on the inner token-aware delay to observe the request in the latter case. Then deliberately omit token forwarding in a separate experiment and explain which observation point was lost. The task is to follow ownership across layers, not to scatter unrelated cancellation sources throughout the code.
- Name the source after the lifetime it represents, such as a screen operation or an application shutdown.
- List every awaited API and state whether it receives and honors the owning token.
Document where cooperation occurs
A useful cancellation contract identifies the owner, the token path, the observation points, and the caller’s expected canceled outcome. Responsiveness depends on where the implementation checks and which awaited APIs support the token, so a long synchronous section can delay cooperation. The sample uses only managed token and timer APIs available in this runtime; it does not assume a desktop thread can be aborted. Preserve that precise model when adding Stop controls, shared tasks, or several concurrent operations with different lifetimes.
- Baseline output is only
cancelled; seven is not returned. - The source requests cancellation, and ReadAsync plus Task.Delay are responsible for observing it.
- Cancel, Dispose, stopping a wait, and forcibly terminating a host are different lifecycle actions.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using var source = new CancellationTokenSource();
source.Cancel();
try { Console.WriteLine(await ReadAsync(source.Token)); }
catch (OperationCanceledException) { Console.WriteLine("cancelled"); }
async Task<int> ReadAsync(CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.Delay(1, token);
return 7;
}
Expected output
cancelled
Cancellation is cooperative; pass the caller’s token through.