mhd
mhd

Reputation: 444

How to use CancellationToken with HttpClient

I'm trying to setup a timeout for each separate HttpClient.Send call in .net8.0.

So I used CancellationTokenSource but It seems that it starts the timer as soon as I create it instead of starting the timer when calling HttpClient Send method. Here is my unit tests result:

[TestMethod]
[DataRow(1)]
[DataRow(10)]
[DataRow(100)]
[DataRow(1000)]
[DataRow(10000)]
[DataRow(100000)]
[DataRow(1000000)]
public void HttpClient_Send_CancellationTokenSource(int timeout)
{
    var httpClient = new HttpClient();
    var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));

    var request = new HttpRequestMessage(HttpMethod.Get, "https://google.com");
    Thread.Sleep(2 * timeout);
    var response = httpClient.Send(request, cancellationToken: cts.Token);
}

So all unit tests failed with this exception: System.Threading.Tasks.TaskCanceledException: The operation was canceled. ---> System.OperationCanceledException: The operation was canceled.

Upvotes: -1

Views: 799

Answers (1)

Guru Stron
Guru Stron

Reputation: 143393

That is how CancellationTokenSource with delay works. From the remarks in the constructor docs:

The countdown for the delay starts during the call to the constructor. When the delay expires, the constructed CancellationTokenSource is canceled, if it has not been canceled already.

So you have basically two options - starting the countdown a little bit before the "sending", by creating the source right before the call to Send:

Thread.Sleep(2 * timeout);
// move here
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));
var response = httpClient.Send(request, cancellationToken: cts.Token);

or a bit "after" the "sending" has started if you switch to async flow + CancelAfter:

var cts = new CancellationTokenSource();
var task = httpClient.SendAsync(request, cancellationToken: cts.Token);
cts.CancelAfter(timeout);
var response = await task;

P.S.

Note that this API highly likely is affected by the same resolution "problems" as Task.Delay, to quote from it's docs:

This method depends on the system clock. This means that the time delay will approximately equal the resolution of the system clock if the millisecondsDelay argument is less than the resolution of the system clock, which is approximately 15 milliseconds on Windows systems.

Hence your test would potentially fail for small values of timeout even if everything would work as you have expected.

Also note that you can specify timeout when creating the HttpClient (though in modern .NET using IHttpClientFactory to create HttpClient usually is the preferred approach, then just manually creating one):

var httpClient = new HttpClient()
{
    Timeout = TimeSpan.FromMilliseconds(15)
};

Upvotes: 3

Related Questions