103

recently I came across this blog post from asp.net monsters which talks about issues with using HttpClientin following way:

using(var client = new HttpClient())
{
}

As per the blog post, if we dispose the HttpClient after every request it can keep the TCP connections open. This can potentially lead to System.Net.Sockets.SocketException.

The correct way as per the post is to create a single instance of HttpClient as it helps to reduce waste of sockets.

From the post:

If we share a single instance of HttpClient then we can reduce the waste of sockets by reusing them:

namespace ConsoleApplication
{
    public class Program
    {
        private static HttpClient Client = new HttpClient();
        public static void Main(string[] args)
        {
            Console.WriteLine("Starting connections");
            for(int i = 0; i<10; i++)
            {
                var result = Client.GetAsync("http://aspnetmonsters.com").Result;
                Console.WriteLine(result.StatusCode);
            }
            Console.WriteLine("Connections done");
            Console.ReadLine();
        }
    }
}

I have always disposed HttpClient object after using it as I felt this is the best way of using it. But this blog post now makes me feel I was doing it wrong all this long.

Should we create a new single instance of HttpClient for all requests? Are there any pitfalls of using static instance?

8
  • Have you encountered any problems you've attributed to the way you are using it? Commented Sep 7, 2016 at 16:54
  • Maybe check this answer and also this.
    – John Wu
    Commented Sep 7, 2016 at 20:44
  • @whatsisname no I have not but looking at the blog I got feeling that I might be using this wrong all the time. Hence, wanted to understand from fellow developers if they see any issue in either approach. Commented Sep 8, 2016 at 10:57
  • 6
    I have not tried it myself (so not providing this as an answer), but according to microsoft as of .NET Core 2.1 you're supposed to use HttpClientFactory as described on docs.microsoft.com/en-us/dotnet/standard/… Commented Sep 4, 2018 at 7:04
  • (As stated in my answer, just wanted to make it more visible, so I'm writing a short comment.) Static instance will properly handle the tcp connection closing handshake, once you do a Close() or initiate a new Get(). If you just dispose of the client when you're done with it, there will be no one to handle that closing handshake and your ports will all have the TIME_WAIT state, because of that.
    – Mladen B.
    Commented Aug 1, 2019 at 8:19

7 Answers 7

60

It seems like a compelling blog post. However, before making a decision, I would first run the same tests that the blog writer ran, but on your own code. I would also try and find out a bit more about HttpClient and its behavior.

This post states:

An HttpClient instance is a collection of settings applied to all requests executed by that instance. In addition, every HttpClient instance uses its own connection pool, isolating its requests from requests executed by other HttpClient instances.

So what is probably happening when an HttpClient is shared is that the connections are being reused, which is fine if you don't require persistent connections. The only way you're going to know for sure whether or not this matters for your situation is to run your own performance tests.

If you dig, you'll find several other resources that address this issue (including a Microsoft Best Practices article), so it's probably a good idea to implement anyway (with some precautions).

References

9
  • 3
    That's a good extensive list. This is my weekend read. Commented Sep 8, 2016 at 5:24
  • "If you dig, you'll find several other resources that address this issue..." you mean to say the TCP connection open issue? Commented Sep 8, 2016 at 5:30
  • 1
    Short answer: use a static HttpClient. If you need to support DNS changes (of your web server or other servers), then you need to worry about timeout settings.
    – Jess
    Commented Mar 28, 2017 at 20:36
  • 10
    It's a testament to how messed up HttpClient is that using it is a "weekend read" as commented by @AnkitVijay.
    – usr
    Commented Oct 22, 2017 at 14:11
  • 1
    we are busy experiencing extensive issues around this, some of out live severs are throwing the out of sockets exception. We have tried both static and using version of http client. However only solution is to reboot the web app every hour. Commented May 19, 2019 at 16:52
38

I'm late to the party, but here is my learning journey on this tricky topic.

1. Where can we find the official advocate on reusing HttpClient?

I mean, if reusing HttpClient is intended and doing so is important, such advocate is better documented in its own API documentation, rather than being hidden in lots of "Advanced Topics", "Performance (anti)pattern" or other blog posts out there. Otherwise how is a new learner supposed to know it before it is too late?

As of now (May 2018), the first search result when googling "c# httpclient" points to this API reference page on MSDN, which does not mention that intention at all. Well, lesson 1 here for newbie is, always click the "Other Versions" link right after the MSDN help page headline, you will probably find links to the "current version" there. In this HttpClient case, it will bring you to the latest document here containing that intention description.

I suspect many developers who was new to this topic did not find the correct documentation page either, that's why this knowledge is not widely spread, and people were surprised when they found it out later, possibly in a hard way.

2. The (mis?)conception of using IDisposable

This one is slightly off-topic but still worth pointing out that, it is not a coincidence to see people in those aforementioned blog posts blaming how HttpClient 's IDisposable interface makes them tend to use the using (var client = new HttpClient()) {...} pattern and then lead to the problem.

I believe that comes down to an unspoken (mis?)conception: "an IDisposable object is expected to be short-lived".

HOWEVER, while it certainly looks like a short-lived thing when we write code in this style:

using (var foo = new SomeDisposableObject())
{
    ...
}

the official documentation on IDisposable never mentions IDisposable objects have to be short-lived. By definition, IDisposable is merely a mechanism to allow you to release unmanaged resources. Nothing more. In that sense, you are EXPECTED to eventually trigger the disposal, but it does not require you to do so in a short-lived fashion.

It is therefore your job to properly choose when to trigger the disposal, base on your real object's life cycle requirement. There is nothing stopping you from using an IDisposable in a long-lived way:

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");

            using (var client = new HttpClient())
            {
                for (...) { ... }  // A really long loop

                // Or you may even somehow start a daemon here

            }

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

With this new understanding, now we revisit that blog post, we can clearly notice that the "fix" initializes HttpClient once but never dispose it, that is why we can see from its netstat output that, the connection remains at ESTABLISHED state which means it has NOT been properly closed. If it were closed, its state would be in TIME_WAIT instead. In practice, it is not a big deal to leak only one connection open after your entire program ends, and the blog poster still see a performance gain after the fix; but still, it is conceptually incorrect to blame IDisposable and choose to NOT dispose it.

3. Do we have to put HttpClient into a static property, or even put it as a singleton?

Based on the understanding of the previous section, I think the answer here becomes clear: "not necessarily". It really depends on how you organize your code, as long as you reuse an HttpClient AND (ideally) dispose it eventually.

Hilariously, not even the example in the Remarks section of the current official document does it strictly right. It defines a "GoodController" class, containing a static HttpClient property that will not be disposed; which disobeys what another example in the Examples section emphasizes: "need to call dispose ... so app doesn't leak resources".

And lastly, singleton is not without its own challenges.

"How many people think global variable is a good idea? No one.

How many people think singleton is a good idea? A few.

What gives? Singletons are just a bunch of global variables."

-- Quoted from this inspiring talk, "Global State and Singletons"

PS: SqlConnection

This one is irrelevant to the current Q&A, but it is probably a good-to-know. SqlConnection usage pattern is different. You do NOT need to reuse SqlConnection, because it will handle its connection pool better that way.

The difference is caused by their implementation approach. Each HttpClient instance uses its own connection pool (quoted from here); but SqlConnection itself is managed by a central connection pool, according to this.

And you still need to dispose SqlConnection, same as you are supposed to do for HttpClient.

25

I did some tests and saw performance improvements with static HttpClient. I used below code for my testing:

namespace HttpClientTest
{
    using System;
    using System.Net.Http;

    class Program
    {
        private static readonly int _connections = 10;
        private static readonly HttpClient _httpClient = new HttpClient();

        private static void Main()
        {
            TestHttpClientWithStaticInstance();
            TestHttpClientWithUsing();
        }

        private static void TestHttpClientWithUsing()
        {
            try
            {
                for (var i = 0; i < _connections; i++)
                {
                    using (var httpClient = new HttpClient())
                    {
                        var result = httpClient.GetAsync(new Uri("http://bing.com")).Result;
                    }
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
            }
        }

        private static void TestHttpClientWithStaticInstance()
        {
            try
            {
                for (var i = 0; i < _connections; i++)
                {
                    var result = _httpClient.GetAsync(new Uri("http://bing.com")).Result;
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
            }
        }
    }
}

For testing:

  • I ran the code with 10, 100, 1000 and 1000 connections.
  • Ran each test 3 times to find out the average.
  • Executed one method at a time

I found the performance improvement between 40% to 60% using static HttpClient instead of disposing it for HttpClient request. I have put the details of the performance test result in the blog post here.

4

To properly close the TCP connection, we need to complete a FIN - FIN+ACK - ACK packet sequence (just like SYN - SYN+ACK - ACK, when opening a TCP connection). If we just call a .Close() method (usually happens when an HttpClient is disposing), and we don't wait for the remote side to confirm our close request (with FIN+ACK), we end up with the TIME_WAIT state on the local TCP port, because we disposed our listener (HttpClient) and we never got the chance to reset the port state to a proper closed state, once the remote peer sends us the FIN+ACK packet.

The proper way to close the TCP connection would be to call the .Close() method and wait for the close event from the other side (FIN+ACK) to arrive on our side. Only then we can send our final ACK and dispose the HttpClient.

Just to add, it makes sense to keep TCP connections open, if you are performing HTTP requests, because of the "Connection: Keep-Alive" HTTP header. Further more, you might ask the remote peer to close the connection for you, instead, by setting the HTTP header "Connection: Close". That way, your local ports will always be properly closed, instead of being in a TIME_WAIT state.

2

Here is a basic API client that uses the HttpClient and HttpClientHandler efficiently. When you create a new HttpClient to make a request there is a lot of overhead. Do NOT recreate HttpClient for each request. Reuse HttpClient as much as possible...

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
//You need to install package Newtonsoft.Json > https://www.nuget.org/packages/Newtonsoft.Json/
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;


public class MyApiClient : IDisposable
{
    private readonly TimeSpan _timeout;
    private HttpClient _httpClient;
    private HttpClientHandler _httpClientHandler;
    private readonly string _baseUrl;
    private const string ClientUserAgent = "my-api-client-v1";
    private const string MediaTypeJson = "application/json";

    public MyApiClient(string baseUrl, TimeSpan? timeout = null)
    {
        _baseUrl = NormalizeBaseUrl(baseUrl);
        _timeout = timeout ?? TimeSpan.FromSeconds(90);    
    }

    public async Task<string> PostAsync(string url, object input)
    {
        EnsureHttpClientCreated();

        using (var requestContent = new StringContent(ConvertToJsonString(input), Encoding.UTF8, MediaTypeJson))
        {
            using (var response = await _httpClient.PostAsync(url, requestContent))
            {
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
        }
    }

    public async Task<TResult> PostAsync<TResult>(string url, object input) where TResult : class, new()
    {
        var strResponse = await PostAsync(url, input);

        return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });
    }

    public async Task<TResult> GetAsync<TResult>(string url) where TResult : class, new()
    {
        var strResponse = await GetAsync(url);

        return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });
    }

    public async Task<string> GetAsync(string url)
    {
        EnsureHttpClientCreated();

        using (var response = await _httpClient.GetAsync(url))
        {
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
    }

    public async Task<string> PutAsync(string url, object input)
    {
        return await PutAsync(url, new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, MediaTypeJson));
    }

    public async Task<string> PutAsync(string url, HttpContent content)
    {
        EnsureHttpClientCreated();

        using (var response = await _httpClient.PutAsync(url, content))
        {
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
    }

    public async Task<string> DeleteAsync(string url)
    {
        EnsureHttpClientCreated();

        using (var response = await _httpClient.DeleteAsync(url))
        {
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
    }

    public void Dispose()
    {
        _httpClientHandler?.Dispose();
        _httpClient?.Dispose();
    }

    private void CreateHttpClient()
    {
        _httpClientHandler = new HttpClientHandler
        {
            AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
        };

        _httpClient = new HttpClient(_httpClientHandler, false)
        {
            Timeout = _timeout
        };

        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);

        if (!string.IsNullOrWhiteSpace(_baseUrl))
        {
            _httpClient.BaseAddress = new Uri(_baseUrl);
        }

        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
    }

    private void EnsureHttpClientCreated()
    {
        if (_httpClient == null)
        {
            CreateHttpClient();
        }
    }

    private static string ConvertToJsonString(object obj)
    {
        if (obj == null)
        {
            return string.Empty;
        }

        return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });
    }

    private static string NormalizeBaseUrl(string url)
    {
        return url.EndsWith("/") ? url : url + "/";
    }
}

Usage:

using (var client = new MyApiClient("http://localhost:8080"))
{
    var response = client.GetAsync("api/users/findByUsername?username=alper").Result;
    var userResponse = client.GetAsync<MyUser>("api/users/findByUsername?username=alper").Result;
}
3
  • U didnt include EnsureHttpClientCreated Commented Oct 5, 2021 at 23:25
  • EnsureHttpClientCreated is there. Commented Oct 6, 2021 at 6:19
  • Is there any reason why you used .Result instead of await? Commented Jan 19, 2023 at 4:58
0

I’d recommend an experiment. Download 1000 pages with one method, then with the other. Does it make a difference? You pick the number 1000 high enough so you can say either “if it failed with too many pages, then it would have failed with 1000” or “if 1000 pages work, then it’s good enough for me”.

-4

There is no one way to use the HttpClient class. The key is to architect your application in a way that makes sense for its environment and constraints.

HTTP is a great protocol to use when you need to expose public API's. It can also be used effectively for light weight low latency Internal services - although the RPC message queue pattern is often a better choice for internal services.

There is a lot of complexity in doing HTTP well.

Consider the following:

  1. Creating a socket and establishing a TCP connection uses network bandwidth and time.
  2. HTTP/1.1 supports pipeline requests on the same socket. Sending multiple requests one after the other, without needing to wait for the previous responses - this is probably responsible for the speed improvement reported by the Blog post.
  3. Caching and load balancer - if you have a load balancer in front of the servers, then ensuring your requests have appropriate cache headers can reduce the load on your servers, and get the responses to the clients quicker.
  4. Don't ever poll a resource, use HTTP chunking to return periodic responses.

But above all, test, measure and confirm. If it's not behaving as designed, then we can answer specific questions about how to achieve your expected results.

7
  • 4
    This doesn't actually answer anything asked. Commented Sep 7, 2016 at 21:43
  • You seem to assume that there is ONE correct way. I don't think there is. I know you have to use it in way that is appropriate, then test and measure how it behaves, and then adjust your approach till you are happy. Commented Sep 7, 2016 at 21:46
  • You wrote a bit about using whether to use HTTP or not to communicate. The OP asked about how best to use a particular library component. Commented Sep 7, 2016 at 22:33
  • 1
    @MichaelShaw: HttpClient implements IDisposable. It is therefore not unreasonable to expect it to be a short-lived object that knows how to clean up after itself, suitable for wrapping in a using statement each time you need it. Unfortunately, that's not how it actually works. The blog post that the OP linked clearly demonstrates that there are resources (specifically, TCP socket connections) that live on long after the using statement has gone out of scope and the HttpClient object has presumably been disposed. Commented Sep 7, 2016 at 23:05
  • 1
    I understand that thought process. It's just if you were thinking about HTTP from the architecture point of view, and was intending to make lots of requests to the same service - then you would be thinking about caching and pipelining, and then the thought of making HttpClient a short lived object would simply feel wrong. Likewise, if you are making requests to different servers and would obtain no benefit from keeping the socket live, then disposing of the HttpClient object after its use makes sense. Commented Sep 8, 2016 at 6:00

Not the answer you're looking for? Browse other questions tagged or ask your own question.