I noticed that when using ASP.NET Core's IHttpClientFactory
, the typed client registration method AddHttpClient<TClient,TImplementation>
does two things:
It registers DI for <TClient,TImplementation>
as transient, as if calling services.AddTransient<TClient,TImplementation>
in startup.cs
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?
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.