Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AddHttpClient<TClient,TImplementation> registers as transient, can it handle high volume concurrent requests efficiently?

I noticed that when using ASP.NET Core's IHttpClientFactory, the typed client registration method AddHttpClient<TClient,TImplementation> does two things:

  1. It registers DI for <TClient,TImplementation> as transient, as if calling services.AddTransient<TClient,TImplementation> in startup.cs

  2. It will inject a HttpClient instance of this registered type for each object initiated.

My concern is, if this is configured as transient, will it be able to handle a large number of concurrent TImplementation objects making http calls, because there will be a new HttpClient as well as a new TClient created for every call? These clients will all access the same URL, will sockets be re-used properly?

like image 819
thankyoussd Avatar asked Oct 26 '25 01:10

thankyoussd


1 Answers

As King King has already pointed out the HttpMessageHandler which matters.

To better understanding how does it work I suggest to examine the DefaultHttpClientFactory's source code.

Let's take a look at the CreateClient method:

public HttpClient CreateClient(string name)
{
    if (name == null)
    {
        throw new ArgumentNullException(nameof(name));
    }

    HttpMessageHandler handler = CreateHandler(name);
    var client = new HttpClient(handler, disposeHandler: false);

    HttpClientFactoryOptions options = _optionsMonitor.Get(name);
    for (int i = 0; i < options.HttpClientActions.Count; i++)
    {
        options.HttpClientActions[i](client);
    }

    return client;
}

As you can see it calls CreateHandler:

public HttpMessageHandler CreateHandler(string name)
{
    if (name == null)
    {
        throw new ArgumentNullException(nameof(name));
    }

    ActiveHandlerTrackingEntry entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;

    StartHandlerEntryTimer(entry);

    return entry.Handler;
}

Here we have a pool of handlers via _activeHandlers. And a factory method _entryFactory, which is called when a given entry does not exist. Let's take a look at their definitions:

_activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal);
_entryFactory = (name) =>
{
    return new Lazy<ActiveHandlerTrackingEntry>(() =>
    {
        return CreateHandlerEntry(name);
    }, LazyThreadSafetyMode.ExecutionAndPublication);
};

_expiredHandlers = new ConcurrentQueue<ExpiredHandlerTrackingEntry>();

So, as you can see it uses a Lazy structure to minimize the cost of the initializations.

The related CreateHandlerEntry's source code can be found here if you are interested.

I also suggest to read Stephen Gordon's excellent article about this topic.

like image 143
Peter Csala Avatar answered Oct 28 '25 16:10

Peter Csala



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!