skip to Main Content

I have the below piece of code which I ran on .Net 4.7.2 and .Net Core but I have got different behavior for each framework

public class Program
{


    private HttpClient Client = new HttpClient();
    public static async Task Main(string[] args)
    {
        Program example = new Program();

        Console.WriteLine("Starting connections");
        int numberofIterations = 10;
        Task<HttpResponseMessage>[] awaitableTasks = new Task<HttpResponseMessage>[numberofIterations];
        for (int i = 0; i < numberofIterations; i++)
        {
            var httpRequestMessage = new HttpRequestMessage();
            httpRequestMessage.RequestUri = new Uri("https://example.com");
            httpRequestMessage.Method = new HttpMethod("GET");

            awaitableTasks[i] = example.Client.SendAsync(httpRequestMessage);
            //Console.WriteLine(result.StatusCode);
        }

        Console.WriteLine("Connections done");
        await Task.WhenAll(awaitableTasks);

    }
}

With the .Net Core framework, the network traces shows a separate tcp connection for each request while with the.NEt 4.7.2 framework the sockets get reused.

Network Trace .Net Core

enter image description here

Network Trace .Net 4.7.2

enter image description here

Appreciate your thoughts to understand the differences, to explain this behavior and the best way to overcome this issue.

2

Answers


  1. Chosen as BEST ANSWER

    Short Answer, the below modification to the code will force my .net core app to not create more than two sockets and reuse them.

         var socketsHandler = new SocketsHttpHandler
                {
                    PooledConnectionLifetime = TimeSpan.FromSeconds(60),
                    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(20),
                    MaxConnectionsPerServer = 2
                };
    
         HttpClientHandler handler = new HttpClientHandler() { MaxConnectionsPerServer = 2 };
    
         var Client = new HttpClient(handler);
    

    Updated Answer


    More Details:

    The .Net Framework 4.x.x HttpClient implementation is built on top of HttpWebRequest and ServicePoint which can be managed by the ServicePointManager. The ServicePointManager has a default connection limit set to 10 for ASP.NET hosted applications and 2 for all others, and that is why in my above example the 2 sockets were opened and reused as the application were prevented to create more than 2 connections(sockets) per service point.

    See the references below for a bit more understanding

    https://learn.microsoft.com/en-us/dotnet/api/system.net.servicepointmanager.defaultconnectionlimit?view=net-6.0#system-net-servicepointmanager-defaultconnectionlimit

    https://learn.microsoft.com/en-us/dotnet/api/system.net.servicepointmanager?view=net-6.0

    https://learn.microsoft.com/en-us/dotnet/api/system.net.servicepoint?view=net-6.0

    C# Does Each Instance of HttpClient Get it's Own ServicePoint

    In .Net Core, the implementation has been changed multiple times, no longer managed by the ServicePointManager and has no default connection limit. The below article has the full story of the HttpClient Class.

    https://www.stevejgordon.co.uk/httpclient-connection-pooling-in-dotnet-core

    Thanks to @user700390 and @PanagiotisKanavos and @JeremyLakeman for their help and guidance toward getting the answer to this question.


  2. Have you seen this resource:

    Using HttpClientFactory without dependency injection

    If you are on .NET Core – you should use a single HttpClient directly
    and set SocketsHttpHandler.PooledConnectionTimeout here to an
    appropriate value.

    If you are on .NET Framework – you should use a single HttpClient and
    use ServicePoint to configure the similar settings.

    The good news for anyone interested in connection management is that
    .NET now has reasonable behavior on Linux (as of 2.1 and
    SocketsHttpHandler) but it requires configuration.

    More detailed information found in https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1#alternatives-to-ihttpclientfactory-2

    There are alternative ways to solve the preceding problems using a
    long-lived SocketsHttpHandler instance.

    Create an instance of SocketsHttpHandler when the app starts and use
    it for the life of the app. Configure PooledConnectionLifetime to an
    appropriate value based on DNS refresh times. Create HttpClient
    instances using new HttpClient(handler, disposeHandler: false) as
    needed. The preceding approaches solve the resource management
    problems that IHttpClientFactory solves in a similar way.

    The SocketsHttpHandler shares connections across HttpClient instances.
    This sharing prevents socket exhaustion. The SocketsHttpHandler cycles
    connections according to PooledConnectionLifetime to avoid stale DNS
    problems.

    Also possibly relevant (but more focused on DI):

    Use IHttpClientFactory to implement resilient HTTP requests

    Here are a few relevant excerpts:

    The original and well-known HttpClient class can be easily used, but
    in some cases, it isn’t being properly used by many developers.

    Though this class implements IDisposable, declaring and instantiating it
    within a using statement is not preferred because when the HttpClient
    object gets disposed of, the underlying socket is not immediately
    released, which can lead to a socket exhaustion problem. For more
    information about this issue, see the blog post You’re using
    HttpClient wrong and it’s destabilizing your software.

    ….

    HttpClient lifetimes

    Each time you get an HttpClient object from the IHttpClientFactory, a
    new instance is returned. But each HttpClient uses an
    HttpMessageHandler that’s pooled and reused by the IHttpClientFactory
    to reduce resource consumption, as long as the HttpMessageHandler’s
    lifetime hasn’t expired.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search