Dynamically changing HttpClient.Timeout in .NET
Asked Answered
H

4

25

I need to change a HttpClient.Timeout property after it made a request(s). When I try, I get an exception:

This instance has already started one or more requests. Properties can only be modified before sending the first request.

Is there any way to avoid this?

Hemelytron answered 21/5, 2014 at 18:17 Comment(3)
Set it before the request??Sienkiewicz
@L.B., The message is clear. But I still wonder is there any trick to avoid this. geedubb, I need to change after.Hemelytron
@ValeO the value of Timeout is used to set CancelAfter on the CancellationTokenSource before the async task is started (internally). So, even if you could change it afterwards, through some "trick", it would have no effect.Supersession
F
13

There isn't much you can do to change this. This is just default behavior in the HttpClient implementation.

The Timeout property must be set before the GetRequestStream or GetResponse method is called. From HttpClient.Timeout Remark Section

In order to change the timeout, it would be best to create a new instance of an HttpClient.

client = new HttpClient();
client.Timeout = 20; //set new timeout
Fortitude answered 21/5, 2014 at 18:25 Comment(2)
Beware: creating/disposing the HttpClient might cause socket exhaustion problems: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrongUniaxial
I'd recommend against this. See argument in link provided by @Monsignor. You can use this approach instead: thomaslevesque.com/2018/02/25/…Wry
U
20

Internally the Timeout property is used to set up a CancellationTokenSource which will abort the async operation when that timeout is reached. Since some overloads of the HttpClient methods accept CancellationTokens, we can create helper methods to have a custom timeouts for specific operations:

public async Task<string> GetStringAsync(string requestUri, TimeSpan timeout)
{
    using (var cts = new CancellationTokenSource(timeout))
    {
        HttpResponseMessage response = await _httpClient.GetAsync(requestUri, cts.Token)
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync();
    }
}
Uniaxial answered 20/9, 2017 at 8:56 Comment(1)
Worth noting that any Timeout set on the HttpClient will still be enforced. This can only reduce the time it takes for a request to timeout; it can't increase it.Durga
F
13

There isn't much you can do to change this. This is just default behavior in the HttpClient implementation.

The Timeout property must be set before the GetRequestStream or GetResponse method is called. From HttpClient.Timeout Remark Section

In order to change the timeout, it would be best to create a new instance of an HttpClient.

client = new HttpClient();
client.Timeout = 20; //set new timeout
Fortitude answered 21/5, 2014 at 18:25 Comment(2)
Beware: creating/disposing the HttpClient might cause socket exhaustion problems: aspnetmonsters.com/2016/08/2016-08-27-httpclientwrongUniaxial
I'd recommend against this. See argument in link provided by @Monsignor. You can use this approach instead: thomaslevesque.com/2018/02/25/…Wry
D
6

Lack of support for custom request-level timeouts has always been a shortcoming of HttpClient in my mind. If you don't mind a small library dependency, Flurl.Http [disclaimer: I'm the author] supports this directly:

"http://api.com/endpoint".WithTimeout(30).GetJsonAsync<T>();

This is a true request-level setting; all calls to the same host use a shared HttpClient instance under the hood, and concurrent calls with different timeouts will not conflict. There's a configurable global default (100 seconds initially, same as HttpClient).

Distraint answered 3/11, 2017 at 1:54 Comment(0)
D
1

Expanding on the answer by @Monsignor and the reply by @andrew-bennet, this is how I solved it:

  • Leave the timeout of HTTPClient alone (100s by default) or set it up to anything larger than you will ever need, since it cannot be changed.
  • Make all the calls using HTTPClient with a custom CancellationToken with your desired timeout.
public class RestService : IRestService
{
    protected readonly HttpClient Client;
    protected static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5);

    public async Task<T> GetAsync<T>(string uri, TimeSpan? customTimeOut = null)
    {
[...]
        CancellationToken cancellationToken = GetCancellationToken(customTimeOut);

        try
        {
                response = await Client.GetAsync(uri, cancellationToken);
        }
[...]
        return result;
    }
[...]
    private static CancellationToken GetCancellationToken(TimeSpan? customTimeOut)
    {
        CancellationTokenSource cts = customTimeOut.HasValue
            ? new CancellationTokenSource(customTimeOut.Value)
            : new CancellationTokenSource(DefaultTimeout);
        CancellationToken cancellationToken = cts.Token;
        return cancellationToken;
    }
}

Perhaps you could reuse the CancellationTokenSource, but I didn't want to complicate the sample further.

Despondent answered 8/7, 2024 at 9:47 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.