System.Threading.Tasks.TaskCanceledException
A TaskCanceledException is thrown when you await a task that was canceled. It derives from OperationCanceledException, and the two are often mixed up in catch blocks.
The most common source is HttpClient. A request that takes longer than the Timeout of the client is canceled, and the message says The request was canceled due to the configured HttpClient.Timeout. It also happens when a CancellationToken is canceled on purpose, for example when a user closes the browser tab.
Minimum version: >= 4.0 >= Core 1.0
Statistics
Common causes
A cancellation is either a timeout or a token that was canceled. The first thing to find out is which one it was.
An HttpClient request that times out
The default timeout of an HttpClient is 100 seconds. If the server is slow, or the network is down and the connection hangs, the request is canceled when the time runs out. The exception does not say that it was a timeout in older versions of .NET, where you only get A task was canceled.
var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
var json = await client.GetStringAsync("https://api.example.com/prices"); // TaskCanceledException if no response in 30 seconds
Find out why the server is slow first. If the call is legitimately slow, give it more time, per request, with a cancellation token:
var client = new HttpClient { Timeout = Timeout.InfiniteTimeSpan };
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
var json = await client.GetStringAsync("https://api.example.com/export", cts.Token);
A CancellationTokenSource that was canceled
If a token is passed down to the task and the source is canceled, or CancelAfter time runs out, the task throws when it reaches the next place that checks the token.
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
await Task.Delay(TimeSpan.FromSeconds(5), cts.Token); // TaskCanceledException
Make sure the time you allow is long enough for the work, and treat a cancellation you asked for as a normal result:
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
try
{
await Task.Delay(TimeSpan.FromSeconds(5), cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Stopped after the time limit.");
}
A request that the client gave up on
In ASP.NET Core, HttpContext.RequestAborted is canceled when the browser or the caller disconnects. If you pass it on to database and HTTP calls, they throw when the caller leaves. This is expected, and not a problem in your code.
Do not log it as an error. Let the exception end the request, or handle OperationCanceledException with a filter on the token.
How to fix it and prevent it
Tell a timeout from a cancellation
Check whether your own token was canceled. If it was not, the cancellation came from somewhere else, most likely a timeout.
public async Task<string> GetPrices(HttpClient client, CancellationToken cancellationToken)
{
try
{
return await client.GetStringAsync("https://api.example.com/prices", cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw; // the caller canceled, not an error
}
catch (TaskCanceledException e)
{
throw new TimeoutException("The price service did not answer in time.", e);
}
}
Add retries and a timeout policy
For calls to services you do not control, use the resilience handler from Microsoft.Extensions.Http.Resilience. It adds a timeout per attempt, a total timeout and retries, so a slow response does not have to end up as an exception.
builder.Services.AddHttpClient("prices", client => client.BaseAddress = new Uri("https://api.example.com"))
.AddStandardResilienceHandler();
Pass the token on and reuse the client
Pass the CancellationToken to every async call that accepts one, so the work stops when it is no longer needed. Create the HttpClient once, or with IHttpClientFactory, to avoid running out of sockets that make requests slow.
How to read the stack trace
A timeout from HttpClient has an inner TimeoutException, and the message mentions the timeout:
System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 30 seconds elapsing.
---> System.TimeoutException: A task was canceled.
---> System.Threading.Tasks.TaskCanceledException: A task was canceled.
at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.SocketsHttpHandler.<SendAsync>g__CreateHandlerAndSendAsync|115_0(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.GetStringAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
--- End of inner exception stack trace ---
at System.Net.Http.HttpClient.HandleFailure(Exception e, Boolean telemetryStarted, HttpResponseMessage response, CancellationTokenSource cts, CancellationToken cancellationToken, CancellationTokenSource pendingRequestsCts)
at System.Net.Http.HttpClient.GetStringAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
at Shop.Services.PriceClient.GetPrices(String url) in C:\src\Shop\Services\PriceClient.cs:line 12
at Program.<Main>$(String[] args) in C:\src\Shop\Program.cs:line 5
The message says that the timeout of 30 seconds was reached, and the inner TimeoutException confirms it. The frame PriceClient.GetPrices on line 12 of PriceClient.cs is the call that waited. A cancellation from a token has no inner TimeoutException. Check what the service did during those seconds, and not only the client.
Should you catch it?
Catch it when you can do something about a slow dependency, such as retrying or returning a cached value. Catch OperationCanceledException to also include the plain cancellation, because TaskCanceledException derives from it.
Do not swallow it everywhere. A canceled token should stop the work, so rethrow when your own token was canceled.
Find TaskCanceledException 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 trialRelated exceptions
- TimeoutException is what you get for timeouts that do not use cancellation.
- HttpRequestException is thrown when the request fails before a timeout, for example when the server cannot be reached.
- AggregateException wraps it when you block on a task that was canceled.
Frequently asked questions
What is the difference between TaskCanceledException and OperationCanceledException?
TaskCanceledException derives from OperationCanceledException. It is thrown when a Task was canceled, while the base type is thrown by code that checks a token. Catch the base type to handle both.
How do I know if it was a timeout?
In current versions of .NET, the message says configured HttpClient.Timeout and the inner exception is a TimeoutException. Otherwise, check whether your own token is canceled. If it is not, assume a timeout.
Should I increase the HttpClient timeout?
Only after you know why the call is slow. A longer timeout keeps threads and connections busy for longer, and a slow server may still not answer. A retry with a short timeout often works better.
Why do I see these errors when users close the page?
The request token is canceled when the caller disconnects, and the calls that use it throw. Filter these out of your error logging.
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 TaskCanceledException for you. For example:
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
If you add ContinueWith() with an empty action, the exception isn't thrown. The exception is caught and passed to the tsk.Exception property in the ContinueWith(). But It saves you from writing a try/catch that uglifies your code.
await Task.Delay(500, cancellationToken.Token).ContinueWith(tsk => { });
UPDATE:
Instead of writing code to handle the exception, a boolean would be much cleaner. This is only preferred when a delay cancel is expected!. One way is to create a helper class (Although I don't like helper classes much)
namespace System.Threading.Tasks
{
public static class TaskDelay
{
public static Task<bool> Wait(TimeSpan timeout, CancellationToken token) =>
Task.Delay(timeout, token).ContinueWith(tsk => tsk.Exception == default);
public static Task<bool> Wait(int timeoutMs, CancellationToken token) =>
Task.Delay(timeoutMs, token).ContinueWith(tsk => tsk.Exception == default);
}
}
For example:
var source = new CancellationTokenSource();
if(!await TaskDelay.Wait(2000, source.Token))
{
// The Delay task was canceled.
}
(don't forget to dispose the source)
By Jeroen van Langen. 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.
Your problem is that SmtpClient is disposed before the email is sent.
Two ways:
Await the
SendMailAsyncresultusing (var client = new SmtpClient()) { await client.SendMailAsync(email); }Register the
SendCompletedevent and dispose theSmtpClientonly after the message is sentvar client = new SmtpClient(); client.SendCompleted += (s, e) => { client.Dispose(); }; return client.SendMailAsync(message);
By meziantou. Read the original answer on Stack Overflow.
That's to be expected. When you cancel the old Delay, it will raise an exception; that's how cancellation works. You can put a simple try/catch around the Delay to catch the expected exception.
Note that if you want to do time-based logic like this, Rx is a more natural fit than async.
By Stephen Cleary. Read the original answer on Stack Overflow.
Source: Stack Overflow