Back to Blog
C#

C# CancellationToken: How to Cancel Operations Gracefully

c# cancellationtoken: Learn how to use C# CancellationToken to cancel async and sync operations gracefully, handle OperationCanceledException, and manage linked tokens.

CancellationTokenAsync ProgrammingTaskCooperative CancellationC# Concurrency
Illustration of a C# CancellationToken stopping an async operation with a cancel button and a task pipeline.

c# cancellationtoken requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When a user closes a window, cancels a download, or times out a request, the running operation should stop without corrupting state. In C#, CancellationToken is the mechanism that makes this possible. It is not a forced thread abort; it is a cooperative signal that the operation should stop as soon as it is safe to do so. The token is a struct that carries state and is passed through method calls to indicate whether cancellation has been requested. This article explains how to create, consume, and coordinate cancellation tokens in practical C# code.

Creating a CancellationTokenSource and Getting a Token

You never create a CancellationToken directly with new CancellationToken(). Instead, you create a CancellationTokenSource, which owns the token and can trigger cancellation. The source is the object that raises the signal; the token is the read-only handle that you pass to other methods.

using var cts = new CancellationTokenSource(); CancellationToken token = cts.Token;

The Token property returns a struct that can be safely copied and passed around. The source itself is disposable, so when you no longer need cancellation, you should dispose it to release any timer or callback resources it holds. In a typical request-scoped scenario, the source lives as long as the operation you want to cancel.

To actually request cancellation, call cts.Cancel(). This flips the token's state to canceled and triggers any registered callbacks. After cancellation, the token remains canceled; you cannot un-cancel it. If you need a fresh cancellation state, create a new source.

Checking for Cancellation in Your Own Method

If you are writing a method that performs a long-running loop or a series of steps, you need to check the token periodically. The token exposes two members that are commonly used: IsCancellationRequested and ThrowIfCancellationRequested().

IsCancellationRequested returns true if cancellation has been requested. You can use this to exit a loop cleanly without throwing an exception. ThrowIfCancellationRequested() throws an OperationCanceledException if cancellation has been requested. This is useful when you want the method to stop immediately and propagate the cancellation up the call stack.

public async Task ProcessAsync(CancellationToken token) { while (true) { token.ThrowIfCancellationRequested(); // Do a unit of work, then check again await DoWorkAsync(token); } }

Using ThrowIfCancellationRequested is the idiomatic way to signal that the method did not complete because it was canceled. It ensures that the caller can distinguish a normal completion from a cancellation, which is important for error handling.

Passing the Token to Async APIs

Most modern .NET async methods accept a CancellationToken parameter. When you call such a method, you should pass your token along. This allows the underlying operation to observe cancellation and stop early, which is far more efficient than waiting for the operation to finish and then discarding the result.

public async Task FetchDataAsync(CancellationToken token) { using var client = new HttpClient(); var response = await client.GetAsync("https://example.com/data", token); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(token); }

If you omit the token, the HTTP request will continue until it completes or times out, even if the user has already navigated away. Passing the token enables the network stack to abort the request early, saving resources and improving responsiveness.

Handling OperationCanceledException and Cleanup

When a method calls ThrowIfCancellationRequested or an underlying API observes the token, an OperationCanceledException is thrown. You need to catch this exception at the appropriate boundary to perform cleanup or to convert cancellation into a non-exceptional result.

try { await ProcessAsync(token); } catch (OperationCanceledException) { // Log or handle cancellation }

It is important to distinguish between cancellation and a genuine failure. If you catch OperationCanceledException, you should rethrow it when you are not handling it specifically. For example, if you are writing a library method, you should let the exception propagate so the caller can decide how to respond. If you are writing a UI event handler, you might want to swallow it and update the UI to reflect that the operation was canceled.

Cleanup should happen in finally blocks regardless of whether cancellation occurred. Dispose resources, close files, or release locks in a finally block so that the state remains consistent even when the operation is interrupted.

Linking Tokens and Timeouts

Sometimes you need to combine multiple cancellation sources. For example, you might want to cancel an operation when the user requests it, but also automatically cancel it after a timeout. CancellationTokenSource.CreateLinkedTokenSource allows you to create a new source that cancels when any of the input tokens are canceled.

using var timeoutSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( userToken, timeoutSource.Token); try { await LongRunningOperationAsync(linkedCts.Token); } catch (OperationCanceledException) { // Either user canceled or timeout occurred } ```n You can also call `CancelAfter` on a source to set a timeout that cancels the token automatically. This is useful for request timeouts without needing to create a separate timer. ## Performance and Operational Considerations CancellationToken is a struct, so passing it around does not allocate heap memory. However, registering callbacks with `token.Register` does allocate and requires unregistering to avoid memory leaks. In hot paths, prefer checking `IsCancellationRequested` in a loop rather than registering callbacks for every iteration. Using exceptions for cancellation flow control has a cost. Throwing `OperationCanceledException` is appropriate when cancellation is rare or when the operation is deeply nested. If you expect cancellation to happen frequently, consider using `IsCancellationRequested` to return a sentinel value instead of throwing, but this depends on the API contract. In async code, cancellation tokens are essential for avoiding wasted work and improving server scalability. A canceled request that is not observed can keep a connection open and consume memory. By propagating tokens through all layers, you ensure that resources are released promptly. ## Advanced Pattern: Cancellation in a Library vs. Application When you write a library method, you should accept a `CancellationToken` parameter and honor it, but you should not throw an exception if the token is canceled unless the method's contract requires it. The caller decides how to handle cancellation. For example, a `ReadAsync` method might return an incomplete result instead of throwing, if that is meaningful for the protocol. A common pattern is to provide a default token when the caller does not supply one. `CancellationToken.None` is a special token that never cancels. You can use it as a default parameter value, but be aware that it does not allow cancellation. This is better than creating a new source every call. ```csharp public async Task ProcessAsync(CancellationToken token = default) { token = token == default ? CancellationToken.None : token; // Use token }

This pattern keeps the API flexible and avoids forcing callers to think about cancellation unless they need it. However, if you are building a public API that performs I/O, you should almost always accept a token and pass it through, because callers will expect to be able to cancel.

Cancellation and Task.Run

When you use Task.Run to offload work to a thread pool, you can pass a cancellation token to the action. The token is checked before the task starts, and if it is already canceled, the task will not run at all. This is useful for preventing work from starting when the user has already canceled the operation.

try { await Task.Run(() => DoWork(), token); } catch (OperationCanceledException) { // Task was canceled before it started or during execution }

Note that Task.Run will throw an OperationCanceledException if the token is canceled before the task begins. Inside the action, you are responsible for checking the token if you want cooperative cancellation during execution. The token passed to Task.Run does not automatically cancel the action; it only prevents the task from starting if cancellation has already been requested.

Summary of Practical Usage

In real code, you will most often encounter cancellation tokens in async APIs, HTTP clients, database calls, and long-running background services. The key is to propagate the token from the top-level operation down to the lowest-level I/O call. This allows the entire chain to stop early when a user cancels or a timeout occurs. Remember to handle OperationCanceledException at the boundaries where you need to perform cleanup or convert cancellation into a user-friendly message. And when you create a CancellationTokenSource, dispose it to free up resources, especially when you use timeouts or linked tokens.

c# cancellationtoken: Practical Usage and Code Examples | RYUSLOG DEV