C# – How to cancel an HttpClient request

It’s a good idea to provide users with a way to cancel a HttpClient request that’s taking too long. To be able to cancel an HttpClient request, you can pass in a CancellationToken:

private static async Task SendRequest(CancellationToken cancellationToken)
{
	var response = await httpClient.GetAsync("https://localhost:12345/stocks/MSFT", cancellationToken);
	response.EnsureSuccessStatusCode();
	return await response.Content.ReadAsStringAsync();
}
Code language: C# (cs)

To get a CancellationToken, you have to create a CancellationTokenSource:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
await SendRequest(cancellationTokenSource.Token);
Code language: C# (cs)

To actually cancel the request, you have to call CancellationTokenSource.Cancel():

cancellationTokenSource.Cancel();
Code language: C# (cs)

This means you’ll need to make the CancellationTokenSource object available to the appropriate code. For example, if you have a cancel button, you’ll need to make the CancellationTokenSource object available to the cancel button click event handler.

After you’re completely done with the CancellationTokenSource, dispose it directly (because you’ll most likely be using this with UI code and needing to share it with click event handling code, which means you wouldn’t be able to dispose of it with a using block):

cancellationTokenSource.Dispose();
Code language: C# (cs)

If you’re already passing in a CancellationToken to control the timeout per request, you can combine the timeout token with the user cancellation token (as I’ll explain below).

Handling the TaskCanceledException

When you trigger cancellation with CancellationTokenSource.Cancel(), the HttpClient request will throw a TaskCanceledException.

The same type of exception is thrown when a timeout happens. If you want to distinguish the two scenarios (timeout vs canceled), you can check if the user CancellationToken was canceled. This examples filters the exception by checking the IsCancellationRequested property:

try
{
	var response = await httpClient.GetAsync("https://localhost:12345/stocks/MSFT", cancellationToken);
	response.EnsureSuccessStatusCode();
	return await response.Content.ReadAsStringAsync();

}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
	Console.WriteLine("User canceled request");
}
catch (TaskCanceledException)
{
	Console.WriteLine("Request timed out");
}

Code language: C# (cs)

Note: It’s possible for a timeout and user cancellation to happen at the same time. I’d recommend treating it like a user cancellation in that case. That’s why this is checking for user cancellation first.

Cancellation while deserializing the response

When you’re sending an HttpClient request, you’ll almost always do two things:

  • Send the request and await it.
  • Deserialize the response.

To support cancellation during the deserialization step, you can use JsonSerializer.DeserializeAsync() and pass in the cancellation token. This requires deserializing from a stream, so you’ll have to use Content.ReadAsStreamAsync():

using System.Text.Json;

var response = await httpClient.GetAsync("https://localhost:12345/stocks/MSFT", cancellationToken);
response.EnsureSuccessStatusCode();

//Check if they canceled before doing an expensive operation
cancellationToken.ThrowIfCancellationRequested();

using (var stream = await response.Content.ReadAsStreamAsync())
{
	return await JsonSerializer.DeserializeAsync<Stock>(stream, jsonOptions, cancellationToken);
}
Code language: C# (cs)

Note that this is calling cancellationToken.ThrowIfCancellationRequested() as well. In general, when you’re supporting cancellation, you should check if the user canceled before starting the next expensive operation.