boot4life
boot4life

Reputation: 5324

Re-using HttpClient but with a different Timeout setting per request?

In order to reuse open TCP connections with HttpClient you have to share a single instance for all requests.

This means that we cannot simply instantiate HttpClient with different settings (e.g. timeout or headers).

How can we share the connections and use different settings at the same time? This was very easy, in fact the default, with the older HttpWebRequest and WebClient infrastructure.

Note, that simply setting HttpClient.Timeout before making a request is not thread safe and would not work in a concurrent application (e.g. an ASP.NET web site).

Upvotes: 71

Views: 26185

Answers (4)

Alex from Jitbit
Alex from Jitbit

Reputation: 60862

.NET 6 (and later) now includes Task.WaitAsync(TimeSpan) method that you can use.

    await httpClient.SendAsync(request).WaitAsync(TomeSpan.FromSeconds(5));

Gets a Task that will complete when this Task completes or when the specified timeout expires.

https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitasync?view=net-8.0#system-threading-tasks-task-waitasync(system-timespan)

P.S. However, if you decide to use this workaround, please do have a look at this post by Andrew Lock "Just because you stopped waiting for it, doesn't mean the Task stopped running"

https://andrewlock.net/just-because-you-stopped-waiting-for-it-doesnt-mean-the-task-stopped-running/

After the timeout elapses - make sure you abort the request!

Upvotes: 0

acandylevey
acandylevey

Reputation: 326

If you're already using a CancellationToken perhaps from an async API endpoint, you can combine it with another CancellationToken using CancellationTokenSource.CreateLinkedTokenSource.

Here's an example:

public async Task<HttpResponseMessage> SendRequestWithTimeout(HttpRequestMessage request, Timespan timeout, CancellationToken ct){
    var timeoutCt = new CancellationTokenSource(timeout).Token;
    var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutCt).Token;
    return await httpClient.SendAsync(request, linkedCts);
}

Upvotes: -1

Todd Menier
Todd Menier

Reputation: 39369

Under the hood, HttpClient just uses a cancellation token to implement the timeout behavior. You can do the same directly if you want to vary it per request:

using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30));
await httpClient.GetAsync("http://www.google.com", cts.Token);

Note that the default timeout for HttpClient is 100 seconds, and the request will still be canceled at that point even if you've set a higher value at the request level. To fix this, set a "max" timeout on the HttpClient, which can be infinite:

httpClient.Timeout = System.Threading.Timeout.InfiniteTimeSpan;

Upvotes: 116

Brad Albright
Brad Albright

Reputation: 756

The accepted answer is great, but I wanted to give another scenario for anyone looking for this in the future. In my case, I was already using a CancellationTokenSource that would cancel its token when the user chose to cancel. So in that case you can still use this technique by using the CreateLinkedTokenSource method of CancellationTokenSource. So in my scenario the http operation will cancel either by the timeout or the user's intervention. Here's a sample:

public async static Task<HttpResponseMessage> SendRequest(CancellationToken cancellationToken)
{
    var ctsForTimeout = new CancellationTokenSource();
    ctsForTimeout.CancelAfter(TimeSpan.FromSeconds(5));
    var cancellationTokenForTimeout = ctsForTimeout.Token;

    using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cancellationTokenForTimeout))
    {
        try
        {
            return await httpClient.GetAsync("http://asdfadsf", linkedCts.Token);
        }
        catch
        {
            //just for illustration purposes
            if (cancellationTokenForTimeout.IsCancellationRequested)
            {
                Console.WriteLine("timeout");
            }
            else if (cancellationToken.IsCancellationRequested)
            {
                Console.WriteLine("other cancellation token cancelled");
            }
            throw;
        }
    }
}

Upvotes: 15

Related Questions