4

After reading the posts below about recommended usage of HttpClient, I changed my code from instantiating HttpClient per request within a using block to a long-lived object.

Do HttpClient and HttpClientHandler have to be disposed?

What is the overhead of creating a new HttpClient per call in a WebAPI client?

My implementation is part of a low-level api, and would be to make requests from from different parts of the app running on different threads, so thread-safety and concurrency when making requests needs to be guaranteed as well.

I even went on to make it a singleton as below, so there is just one instance of HttpClient used throughout the app. (fourth version form John SKeet's article)

http://csharpindepth.com/Articles/General/Singleton.aspx

public sealed class MyHttpClient
{       
    private static readonly volatile HttpClient _myHttpClient = new HttpClient();

    static MyHttpClient() {}
    private MyHttpClient(){ }

    public static HttpClient MyHttpClientObj
    {
        get
        {               
            return _myHttpClient;
        }
    }
}

And below is an example of how this gets used

  public IEnumerable<string> GetSomeData(string url, FormUrlEncodedContent bodyParameters)
  {
      try
      {
          //is it possible to configure timeout here instead, such that every request will have its one timeout duration?
          var response = MyHttpClient.MyHttpClientObj.PostAsync(url, bodyParameters);

          var result = response.Result;

          if (!result.IsSuccessStatusCode)
          {
             //log and return null
          }

          var data = JsonConvert.DeserializeObject<List<string>>(result.Content.ReadAsStringAsync().Result);

          return data;

      }
      catch (Exception ex)
      {
          //logging exceptions
      }
  }

When making requests via HttpClient, I've made sure to use only the therad-safe methods listed below, but when deserializing the response, result.Content.ReadAsStringAsync().Result is used. This is because the higher level calls don't support async responses yet.

https://msdn.microsoft.com/en-us/library/system.net.http.httpclient(v=vs.110).aspx#Anchor_5

However, I still have a few questions.

  1. Is this approach Thread-safe and stable enough to not cause any memory leaks?
  2. How can I configure the Timeout for every request?
  3. Is it necessary to specify 'Connection: keep-alive' in DefaultHeaders?
  4. How can I add a custom header/modify a default header for every request?
  5. And finally, Are there any known performance issues/drawbacks in using HttpClient this way?
Community
  • 1
  • 1
Ren
  • 1,493
  • 6
  • 31
  • 56

3 Answers3

3

It's thread safe, and recommended.

Depending on your usage, the biggest thing may be to raise the connection limit to your desired level of concurrency:

ServicePointManager.DefaultConnectionLimit = 16;

Without this set, concurrent requests to a single host will sit in a queue until they can be issued. And they'll time out if they're not got to in time.

I'd also recommend using pipelining to improve performance:

new HttpClient(new WebRequestHandler() { AllowPipelining = true });
Cory Nelson
  • 29,236
  • 5
  • 72
  • 110
1
  1. Yes, this approach is thread-safe, as you call a thread-safe methods and do not you any synchronization logic, so your client threads simply independent from each other.
  2. You can use an overload with CancellationTokenSource, with calling it method CancelAfter, this approach is recommended by MSDN.
  3. No, but if your connection do require some interaction between client and server, it is highly recommended approach for HTTP/1.1, it reduces the overhead to recreating the socket connection and some handshakes between participating sides.
  4. You can use the Headers property of the FormUrlEncodedContent class, simply add a header you need to it.
  5. The huge drawback for your solution is .Result call, as it blocks the current thread. You can try to refactor your approach with TaskCompletionSource usage so you could possibly use async methods internally. This will provide you a possibility for the threads to do something else rather than wait for result.
VMAtm
  • 27,943
  • 17
  • 79
  • 125
1

The biggest performance issue you will encounter using HttpClient in a highly concurrent environment is the number of concurrent connections to any given url is limited to 2 by default. You can increase this for all endpoints using ServicePointManager.DefaultConnectionLimit or get a specific ServicePoint using ServicePointManager.FindServicePoint and set ServicePoint.ConnectionLimit

Joe Enzminger
  • 11,110
  • 3
  • 50
  • 75