System.OperationCanceledException

An OperationCanceledException is thrown when an operation notices that its CancellationToken has been canceled. It is how .NET stops async work early, for example when a user closes the browser during a request, or when the application shuts down.

It is not an error in most cases. It is the expected way for work to stop, and the problem is only when it ends up in the error log, or when it is caught in the wrong place. The TaskCanceledException derives from it.

Minimum version: >= 4.0 >= Core 1.0

Statistics

35
elmah.io logo 11

Common causes

Someone asked the operation to stop. These are the usual sources.

A token that was canceled on purpose

Code that calls ThrowIfCancellationRequested, or passes the token to an async method such as Task.Delay, throws as soon as the token is canceled.

using var cts = new CancellationTokenSource();
cts.Cancel();
cts.Token.ThrowIfCancellationRequested();   // OperationCanceledException

Catch it where you started the work, and treat it as a normal end:

using var cts = new CancellationTokenSource();
cts.Cancel();

try
{
    cts.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
    Console.WriteLine("Canceled.");
}

In ASP.NET Core, the token HttpContext.RequestAborted is canceled when the browser or the client disconnects. Database and HTTP calls that use it throw this exception. A load balancer that closes slow requests has the same effect.

Pass the token on, so the work stops, and ignore the exception when it is your own token that was canceled. Do not log it as an error:

public async Task<string> GetPrice(HttpClient client, CancellationToken cancellationToken)
{
    try
    {
        return await client.GetStringAsync("https://api.example.com/price", cancellationToken);
    }
    catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
    {
        return null;
    }
}

Hosted services and background loops get a token that is canceled when the host stops. The delay or the queue they are waiting on throws, which is the signal to leave the loop.

public async Task RunAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
        }
        catch (OperationCanceledException)
        {
            break;
        }
    }
}

The loop above is the pattern: leave the loop, and let the service stop. Do not rethrow, and do not log it as a failure.

How to fix it and prevent it

Filter on the token

An OperationCanceledException from your own token is expected. One from another token, or from a timeout in a library, may not be. Use when (token.IsCancellationRequested) to tell them apart.

If you log unhandled exceptions, skip the canceled ones for requests that were aborted. They are not errors in your application, and they hide the ones that are.

Take a CancellationToken as the last parameter of async methods, and pass it to every call that accepts one. The work then stops as soon as it is no longer needed, instead of finishing for nobody.

public async Task<int> CountAsync(Stream stream, CancellationToken cancellationToken = default)
{
    var buffer = new byte[4096];
    var total = 0;
    int read;

    while ((read = await stream.ReadAsync(buffer, cancellationToken)) > 0)
    {
        total += read;
    }

    return total;
}

How to read the stack trace

The top frames are the framework checking the token. The first frame in your code is the one that was running when it was canceled:

System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowOperationCanceledException()
   at System.Threading.CancellationToken.ThrowIfCancellationRequested()
   at Shop.Services.Importer.Run(CancellationToken token) in C:\src\Shop\Services\Importer.cs:line 16
   at Program.<Main>$(String[] args) in C:\src\Shop\Program.cs:line 7

The check is on line 16 of Importer.cs, and the token was canceled before it got there. The exception does not say who canceled it. Find the CancellationTokenSource that the token comes from, and look at what calls Cancel or sets a timeout on it.

Should you catch it?

Yes, but with care. Catch it where you own the token, and where you know what to do when work stops. Do not catch it everywhere and ignore it, because a canceled operation did not finish, and the code after it must not act as if it did.

Let it pass through code that only passes the token on. Catching it in the middle makes the caller think the work was done.

Find OperationCanceledException before your users do

elmah.io logs every unhandled exception in your .NET application with its stack trace and request details, groups identical errors and notifies you when a new one appears.

Start free trial
Free 21-day trial No credit card required

Related exceptions

Frequently asked questions

Is OperationCanceledException an error?

Not by itself. It means that the work was asked to stop. It is only a problem if you did not expect the cancellation, for example because of a timeout that is too short.

TaskCanceledException derives from OperationCanceledException. Catch the base class to handle both.

The request was aborted, because the client disconnected or a proxy closed the connection. The token of the request is canceled, and the calls that use it throw.

Create the source with a time, new CancellationTokenSource(TimeSpan.FromSeconds(5)), or call CancelAfter. Pass its token to the work.

Let your AI agent track it down

Connect Claude Code, Cursor, VS Code or Visual Studio to the elmah.io MCP server and ask your agent to look into OperationCanceledException for you. For example:

AI chat
Find the most frequent OperationCanceledException in my production log and show me the line that throws it.

The agent reads the stack trace and request details from elmah.io, finds the code in your repository and proposes a fix. In Claude Code, add the server with one command:

claude mcp add --transport http --client-id claudecode elmahio https://mcp.elmah.io/mcp

The MCP server is included on every plan and is currently in beta. Set up the MCP server.

Further reading

YouTube videos

Answers from Stack Overflow

You can implement your work method as follows:

private static void Work(CancellationToken cancelToken)
{
    while (true)
    {
        if(cancelToken.IsCancellationRequested)
        {
            return;
        }
        Console.Write("345");
    }
}

That's it. You always need to handle cancellation by yourself - exit from method when it is appropriate time to exit (so that your work and data is in consistent state)

UPDATE: I prefer not writing while (!cancelToken.IsCancellationRequested) because often there are few exit points where you can stop executing safely across loop body, and loop usually have some logical condition to exit (iterate over all items in collection etc.). So I believe it's better not to mix that conditions as they have different intention.

Cautionary note about avoiding CancellationToken.ThrowIfCancellationRequested():

Comment in question by Eamon Nerbonne:

... replacing ThrowIfCancellationRequested with a bunch of checks for IsCancellationRequested exits gracefully, as this answer says. But that's not just an implementation detail; that affects observable behavior: the task will no longer end in the cancelled state, but in RanToCompletion. And that can affect not just explicit state checks, but also, more subtly, task chaining with e.g. ContinueWith, depending on the TaskContinuationOptions used. I'd say that avoiding ThrowIfCancellationRequested is dangerous advice.

By Sasha. Read the original answer on Stack Overflow.

The difference here comes from using token.ThrowIfCancellationRequested(). This method checks for cancellation and if requested throws OperationCanceledException specifically and not TaskCanceledException (understandable as CancellationToken isn't exclusive to the TPL). You can look at the reference source and see that it calls this method:

private void ThrowOperationCanceledException()
{
    throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this);
}

"Regular" cancellation though will indeed generate a TaskCanceledException. You can see that by cancelling the token before the task had a chance to start running:

cancellationTokenSource.Cancel();
var task = Task.Run(() => { }, cancellationTokenSource.Token);
try
{
    await task; 
}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
    Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}");
    Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}");
    Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}");
}

Output:

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Sandbox.Program.<MainAsync>d__1.MoveNext()
Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null

Traditional .Net methods usually don't use CancellationToken.ThrowIfCancellationRequested for async API as this is only appropriate when offloading work to another thread. These methods are for inherently asynchronous operations so cancellation is monitored using CancellationToken.Register (or the internal InternalRegisterWithoutEC).

By i3arnon. Read the original answer on Stack Overflow.

TaskCanceledException inherits from OperationCanceledException. So it least there is a little consitency.

if( ex is OperationCanceledException)
{
...
}

By Tomas Kubes. Read the original answer on Stack Overflow.

You have to pass the CancellationToken to the Task, which will periodically monitors the token to see whether cancellation is requested.

// CancellationTokenSource provides the token and have authority to cancel the token
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;  

// Task need to be cancelled with CancellationToken 
Task task = Task.Run(async () => {     
  while(!token.IsCancellationRequested) {
      Console.Write("*");         
      await Task.Delay(1000);
  }
}, token);

Console.WriteLine("Press enter to stop the task"); 
Console.ReadLine(); 
cancellationTokenSource.Cancel(); 

In this case, the operation will end when cancellation is requested and the Task will have a RanToCompletion state. If you want to be acknowledged that your task has been cancelled, you have to use ThrowIfCancellationRequested to throw an OperationCanceledException exception.

Task task = Task.Run(async () =>             
{                 
    while (!token.IsCancellationRequested) {
         Console.Write("*");                      
         await Task.Delay(1000);                
    }           
    token.ThrowIfCancellationRequested();               
}, token)
.ContinueWith(t =>
 {
      t.Exception?.Handle(e => true);
      Console.WriteLine("You have canceled the task");
 },TaskContinuationOptions.OnlyOnCanceled);  
 
Console.WriteLine("Press enter to stop the task");                 
Console.ReadLine();                 
cancellationTokenSource.Cancel();                 
task.Wait(); 

Hope this helps to understand better.

By Mahbubur Rahman Manik. Read the original answer on Stack Overflow.

You can create a Task with cancellation token, when you app goto background you can cancel this token.

You can do this in PCL https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/app-lifecycle

var cancelToken = new CancellationTokenSource();
Task.Factory.StartNew(async () => {
    await Task.Delay(10000);
    // call web API
}, cancelToken.Token);

//this stops the Task:
cancelToken.Cancel(false);

Anther solution is user Timer in Xamarin.Forms, stop timer when app goto background https://xamarinhelp.com/xamarin-forms-timer/

By Jesse Jiang. Read the original answer on Stack Overflow.