When you use the same instance of HttpClient for multiple requests (sequential and concurrent) to the same URL, it’ll reuse connections. Requests that get to reuse a connection are 5.5-8.5x faster than requests that have to open a new connection.
There are a few scenarios that benefit from this connection reuse:
- Sending sequential requests frequently.
- Sending concurrent requests frequently.
Table of Contents
Measuring the performance gains
To show the performance gains of keeping a connection alive and reusing it for multiple requests, I’ll send multiple requests to the same URL. The first request will have to open the connection. The subsequent requests will get to reuse the connection.
Naturally, there will be variation in the execution time of the network requests. That’s why I’m sending lots of requests and looking at the average time and min/max.
Whenever measuring performance, it’s a good idea to discard the first measurement (due to the framework warming up). In this case though, we really need the measurement of the first request. That’s why I’m sending a warm up request to a different URL (localhost).
Code
Here is the code I used to measure the performance of individual requests. It’s sending requests sequentially.
public static async Task CostOfOpeningConnection()
{
var publicAPI = "https://api.isevenapi.xyz/api/iseven/6/";
var httpClient = new HttpClient();
//Warm up the framework
await SendRequest(httpClient, "https://localhost:9000/stocks/MSFT");
Stopwatch sw = Stopwatch.StartNew();
await SendRequest(httpClient, publicAPI);
sw.Stop();
Console.WriteLine($"Cost of opening a connection: {sw.ElapsedMilliseconds}ms");
List<double> times = new List<double>();
for (int i = 0; i < 100; i++)
{
sw.Restart();
var content = await SendRequest(httpClient, publicAPI);
sw.Stop();
//Console.WriteLine(content); //if you want to see the response
times.Add(sw.ElapsedMilliseconds);
}
Console.WriteLine($"Cost of reusing a connection: Min={times.Min()}ms Max={times.Max()}ms Avg={times.Average()}ms");
}
public static async Task<string> SendRequest(HttpClient httpClient, string url)
{
var response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
Code language: C# (cs)
Using netstat shows that it only used one connection:
C:\WINDOWS\system32>netstat -an | find "192.241.209.210"
TCP 192.168.0.14:13486 192.241.209.210:443 ESTABLISHED
Code language: plaintext (plaintext)
Performance results
Running the code once gives the following results:
Cost of opening a connection: 618ms
Cost of reusing a connection: Min=74ms Max=85ms Avg=78.4ms
Code language: plaintext (plaintext)
The request that had to open the connection took 618ms. Subsequent requests that were able to reuse the connection took 78.4ms on average, which is about 8x faster than the request that had to open the connection.
I’ve run this multiple times at different times of the day. I’ve used different URLs. The requests that reuse the connection tend to be 5.5-8.5x faster in all variations I’ve tried.