Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpClient cancellation doesn't kill underlying TCP call

I'm trying to set a default timeout for my HttpClient calls to 5 seconds.

I've done this via CancellationTokenSource.

Here's the pertinent bit of code:

var cancellationToken = new CancellationTokenSource();
cancellationToken.CancelAfter(TimeSpan.FromSeconds(5));
var result = _httpClient.SendAsync(request, cancellationToken.Token);

Works as i expected in terms of the calling code getting a "Task was cancelled" error (i tested in a .NET 4.7 console app), but i noticed in Fiddler the request was still running for 1 minute, until it finally gave up:

enter image description here

Can someone explain this behaviour?

I would expect the underlying request to also get cancelled when the cancellation is triggered.

_httpClient is instantiated like: new HttpClient { BaseAddress = baseAddress }

I know there's the the Timeout setting, but not sure if I should be using that or cancellation tokens? My guess is Timeout is for the non-async/await cases?

like image 745
RPM1984 Avatar asked Aug 07 '17 07:08

RPM1984


2 Answers

As Damien said in the comments, HttpClient re-uses connections as much as possible, hence the reason why the connection is not closed on cancel.

When canceling a request like that, the HttpClient will just stop sending/receiving data to/from the other end. It will not send anything to inform the other end that it was cancelled. So the timeout you see of 1 minute depends on the behavior of the other end of your connection.

Also, if you want to cancel each request after 5 seconds, you can as well set the Timeout property of _httpClient to TimeSpan.FromSeconds(5). The behavior will be exactly the same (a TaskCanceledException will be thrown if the other end doesn't respond within 5 seconds).

like image 86
huysentruitw Avatar answered Oct 20 '22 11:10

huysentruitw


If anyone is interested, you can try the following approach to applying your own timeout per HttpClient request. It seems to work for me, restricting the SendAsync() to 2 seconds and returning immediately when the timeout occurs:

private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, TimeSpan? timeout = null)
{
    if (timeout is null)
    {
        return await _httpClient.SendAsync(request);
    }
    else
    {
        using (var cts = new CancellationTokenSource(timeout.Value))
        {
            var sendTask = _httpClient.SendAsync(request);

            while (!sendTask.IsCompleted)
            {
                cts.Token.ThrowIfCancellationRequested();
                await Task.Delay(10).ConfigureAwait(false);
            }

            return await sendTask.ConfigureAwait(false);
        }
    }
}
like image 33
Martin Lottering Avatar answered Oct 20 '22 12:10

Martin Lottering