CancellationToken
exception handling
asynchronous programming
C#
.NET

How to use the CancellationToken without throwing/catching an exception?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Yes, you can use a CancellationToken cooperatively without throwing an exception, but only if your own method chooses that style. In .NET, many built-in async APIs treat cancellation as an exceptional completion and surface it as OperationCanceledException when awaited.

So there are really two patterns:

  • exception-based cancellation, which is common in framework APIs
  • cooperative early-return cancellation, which you can design into your own methods

The Core Non-Exception Pattern

If your method owns the control flow, you can poll the token and return early:

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5static async Task<bool> DoWorkAsync(CancellationToken token)
6{
7    for (int i = 0; i < 10; i++)
8    {
9        if (token.IsCancellationRequested)
10        {
11            Console.WriteLine("Canceled cooperatively.");
12            return false;
13        }
14
15        await Task.Delay(100);
16    }
17
18    Console.WriteLine("Completed.");
19    return true;
20}

Here the method returns false on cancellation instead of throwing. That is a valid design if your callers understand the contract.

Use a Meaningful Return Contract

A bare return works for Task methods, but a richer result is often clearer:

csharp
1public enum WorkResult
2{
3    Completed,
4    Canceled
5}
6
7static async Task<WorkResult> ProcessAsync(CancellationToken token)
8{
9    while (!token.IsCancellationRequested)
10    {
11        await Task.Delay(50);
12        return WorkResult.Completed;
13    }
14
15    return WorkResult.Canceled;
16}

This makes cancellation part of the normal method result instead of something callers must catch.

Be Careful With Built-In Async APIs

This is where many developers get tripped up. Even if your method wants non-exception flow, a framework API may still throw when the token is canceled:

csharp
await Task.Delay(TimeSpan.FromSeconds(5), token);

If token is canceled, Task.Delay completes in a canceled state, and awaiting it throws OperationCanceledException.

So if you truly want a no-exception path, do not blindly pass the token into every awaited API. Instead, you may need to poll the token yourself around operations that do not require cancellation-aware awaiting.

Cooperative Cancellation Example

Here is a more realistic pattern:

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5static async Task<bool> PollingLoopAsync(CancellationToken token)
6{
7    for (int i = 0; i < 20; i++)
8    {
9        if (token.IsCancellationRequested)
10        {
11            return false;
12        }
13
14        await Task.Delay(100); // no token passed here
15    }
16
17    return true;
18}

This keeps the method on a cooperative path. The tradeoff is that cancellation is only observed at explicit checkpoints rather than immediately interrupting awaited operations.

ThrowIfCancellationRequested Is the Opposite Choice

If you write this:

csharp
token.ThrowIfCancellationRequested();

you are explicitly choosing the exception-based model. That is not wrong. It is often the idiomatic choice for async APIs in .NET. It is just not the same design as cooperative return-based cancellation.

So the question is not "which one is always correct?" It is "what contract should this method expose?"

Cleanup Still Matters

Even without exceptions, cancellation still means interrupted work. If your method opens streams, timers, or network resources, clean them up before returning:

csharp
1using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
2
3while (!token.IsCancellationRequested)
4{
5    await timer.WaitForNextTickAsync();
6}
7
8return;

If the awaited API itself is cancellation-aware and throws on cancellation, you are back in the exception-based model. So be deliberate.

Common Pitfalls

The biggest pitfall is assuming CancellationToken itself throws. It does not. Exceptions come from code that chooses to call ThrowIfCancellationRequested or from awaited APIs that represent cancellation that way.

Another common mistake is mixing both models carelessly. A method that mostly returns false for cancellation but occasionally lets OperationCanceledException escape is confusing to call.

People also pass the token into APIs like Task.Delay and then wonder why await still throws. That is expected behavior for those APIs.

Finally, do not ignore cancellation entirely just because you dislike exceptions. Cooperative cancellation still needs checkpoints and a clear contract.

Summary

  • You can use CancellationToken without exceptions if your own method cooperatively checks IsCancellationRequested and returns normally.
  • Many built-in async APIs still signal cancellation by throwing when awaited.
  • Choose a clear contract, such as returning bool or a result enum on cancellation.
  • Do not mix return-based and exception-based cancellation styles accidentally.
  • Cancellation without exceptions is possible, but it is a design choice, not the default behavior of every async API.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.