TaskCanceledException: A Task Was Canceled — How to Fix It (C#)
System.Threading.Tasks.TaskCanceledException: A task was canceled
What causes this
An async operation was canceled before it completed. In C#, this happens in two scenarios:
- Timeout:
HttpClienthas a default 100-second timeout. If the server doesn’t respond in time, the request is canceled. - Explicit cancellation: A
CancellationTokenwas triggered, usually because the user navigated away, the request was aborted, or a parent operation was canceled.
The tricky part: both cases throw TaskCanceledException, but they mean different things and should be handled differently.
Fix 1: Increase the HttpClient timeout
var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(300); // 5 minutes
var response = await client.GetAsync("https://slow-api.example.com/data");
For specific requests that need more time:
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
var response = await client.GetAsync(url, cts.Token);
Fix 2: Distinguish timeout from cancellation
try
{
var response = await client.GetAsync(url, cancellationToken);
}
catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested)
{
// Timeout — the server didn't respond in time
Console.WriteLine("Request timed out");
}
catch (TaskCanceledException)
{
// Explicit cancellation — the caller canceled the operation
Console.WriteLine("Request was canceled by the caller");
}
This pattern checks whether the cancellation came from a timeout or from the token you passed in.
Fix 3: Add retry logic
For transient timeouts, retry with exponential backoff:
using Polly;
var retryPolicy = Policy
.Handle<TaskCanceledException>()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
var response = await retryPolicy.ExecuteAsync(() =>
client.GetAsync(url));
Or without Polly:
for (int i = 0; i < 3; i++)
{
try
{
return await client.GetAsync(url);
}
catch (TaskCanceledException) when (i < 2)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
}
}
Fix 4: Check if the server is actually responding
The timeout might be legitimate — the server is slow or down:
curl -v https://api.example.com/endpoint
If the server is slow, consider:
- Adding a loading indicator in the UI
- Using streaming responses for large payloads
- Optimizing the server-side query
Fix 5: Handle cancellation in ASP.NET
In ASP.NET controllers, the request is canceled when the client disconnects:
[HttpGet]
public async Task<IActionResult> GetData(CancellationToken cancellationToken)
{
try
{
var data = await _service.GetDataAsync(cancellationToken);
return Ok(data);
}
catch (OperationCanceledException)
{
// Client disconnected — this is normal, just log and return
_logger.LogInformation("Client disconnected");
return StatusCode(499); // Client Closed Request
}
}
How to prevent it
- Set explicit timeouts on
HttpClientbased on expected response times — don’t rely on the 100-second default - Always pass
CancellationTokenthrough your async call chain so operations can be canceled cleanly - Use Polly or a similar library for retry policies on external API calls
- Log
TaskCanceledExceptionseparately from other exceptions — they’re often expected behavior, not bugs Related: C# StackOverflowException — How to Fix It