在咱們開發當中常常須要向特定URL地址發送Http請求操做,在.net core 中對httpClient使用不當會形成災難性的問題,這篇文章主要來分享.net core中經過IHttpClientFactory 工廠來使用HttpClient的正確打開方式。html
using(var client = new HttpClient())
咱們能夠先來作一個簡單的測試,代碼以下:windows
public async Task<string> GetBaiduListAsync(string url) { var html = ""; for (var i = 0; i < 10; i++) { using (var client = new System.Net.Http.HttpClient()) { var result=await client.GetStringAsync(url); html += result; } } return html; }
運行項目輸出結果後,經過netstate查看下TCP鏈接狀況:
雖然項目已經運行結束,可是鏈接依然存在,狀態爲" TIME_WAIT"(繼續等待看是否還有延遲的包會傳輸過來;默認在windows下,TIME_WAIT
狀態將會使系統將會保持該鏈接 240s。
在高併發的狀況下,鏈接來不及釋放,socket被耗盡,耗盡以後就會出現喜聞樂見的一個錯誤:網絡
對象所佔用資源應該確保及時被釋放掉,可是,對於網絡鏈接而言,這是錯誤的,緣由有以下:併發
對於上面的錯誤緣由,你們可能會想到使用靜態單例模式的HttpClient,以下:框架
private static HttpClient Client = new HttpClient();
靜態單例模式雖然能夠解決上面問題,可是會帶來另一個問題:socket
HttpClientFactory 以模塊化、可命名、可配置、彈性方式重建了 HttpClient 的使用方式: 由 DI 框架注入 IHttpClientFactory 工廠;由工廠建立 HttpClient 並從內部的 Handler 池分配請求 Handler。async
.net core 2.1 開始引入了IHttpClientFactory 工廠類來自動管理IHttpClientFactory 類的建立和資源釋放,能夠經過Ioc 注入方式進行使用,代碼以下:ide
services.AddControllers(); services.AddHttpClient();
調用代碼以下:模塊化
private readonly IHttpClientFactory _clientFactory; public FirstController(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } /// <summary> /// /// </summary> /// <param name="url"></param> /// <returns></returns> public async Task<string> GetBaiduAsync(string url) { var client = _clientFactory.CreateClient(); var result = await client.GetStringAsync(url); return result; }
代碼中經過IHttpClientFactory
中的CreateClient()
方法進行建立一個HttpClient 對象,可是沒有看到有釋放資源的動做,那它是怎麼釋放的呢?
咱們來看看它的主要源代碼高併發
/// <summary> /// Creates a new <see cref="HttpClient"/> using the default configuration. /// </summary> /// <param name="factory">The <see cref="IHttpClientFactory"/>.</param> /// <returns>An <see cref="HttpClient"/> configured using the default configuration.</returns> public static HttpClient CreateClient(this IHttpClientFactory factory) { if (factory == null) { throw new ArgumentNullException(nameof(factory)); } return factory.CreateClient(Options.DefaultName); } public HttpClient CreateClient(string name) { if (name == null) { throw new ArgumentNullException(nameof(name)); } var handler = CreateHandler(name); var client = new HttpClient(handler, disposeHandler: false); var options = _optionsMonitor.Get(name); for (var i = 0; i < options.HttpClientActions.Count; i++) { options.HttpClientActions[i](client); } return client; } public HttpMessageHandler CreateHandler(string name) { if (name == null) { throw new ArgumentNullException(nameof(name)); } var entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value; StartHandlerEntryTimer(entry); return entry.Handler; }
代碼中能夠看到建立HttpClent
時會先建立HttpMessageHandler
對象,而CreateHandler 方法中調用了StartHandlerEntryTimer
方法,該方法主要時啓動清理釋放定時器方法,核心代碼以下:
public DefaultHttpClientFactory( IServiceProvider services, IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory, IOptionsMonitor<HttpClientFactoryOptions> optionsMonitor, IEnumerable<IHttpMessageHandlerBuilderFilter> filters) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (scopeFactory == null) { throw new ArgumentNullException(nameof(scopeFactory)); } if (loggerFactory == null) { throw new ArgumentNullException(nameof(loggerFactory)); } if (optionsMonitor == null) { throw new ArgumentNullException(nameof(optionsMonitor)); } if (filters == null) { throw new ArgumentNullException(nameof(filters)); } _services = services; _scopeFactory = scopeFactory; _optionsMonitor = optionsMonitor; _filters = filters.ToArray(); _logger = loggerFactory.CreateLogger<DefaultHttpClientFactory>(); // case-sensitive because named options is. _activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal); _entryFactory = (name) => { return new Lazy<ActiveHandlerTrackingEntry>(() => { return CreateHandlerEntry(name); }, LazyThreadSafetyMode.ExecutionAndPublication); }; _expiredHandlers = new ConcurrentQueue<ExpiredHandlerTrackingEntry>(); _expiryCallback = ExpiryTimer_Tick; _cleanupTimerLock = new object(); _cleanupActiveLock = new object(); } // Internal for tests internal void ExpiryTimer_Tick(object state) { var active = (ActiveHandlerTrackingEntry)state; // The timer callback should be the only one removing from the active collection. If we can't find // our entry in the collection, then this is a bug. var removed = _activeHandlers.TryRemove(active.Name, out var found); Debug.Assert(removed, "Entry not found. We should always be able to remove the entry"); Debug.Assert(object.ReferenceEquals(active, found.Value), "Different entry found. The entry should not have been replaced"); // At this point the handler is no longer 'active' and will not be handed out to any new clients. // However we haven't dropped our strong reference to the handler, so we can't yet determine if // there are still any other outstanding references (we know there is at least one). // // We use a different state object to track expired handlers. This allows any other thread that acquired // the 'active' entry to use it without safety problems. var expired = new ExpiredHandlerTrackingEntry(active); _expiredHandlers.Enqueue(expired); Log.HandlerExpired(_logger, active.Name, active.Lifetime); StartCleanupTimer(); } // Internal so it can be overridden in tests internal virtual void StartHandlerEntryTimer(ActiveHandlerTrackingEntry entry) { entry.StartExpiryTimer(_expiryCallback); }
從微軟源碼分析,HttpClient繼承自HttpMessageInvoker,而HttpMessageInvoker實質就是HttpClientHandler。
HttpClientFactory 建立的HttpClient,也便是HttpClientHandler,只是這些個HttpClient被放到了「池子」中,工廠每次在create的時候會自動判斷是新建仍是複用。(默認生命週期爲2min)。 但願這篇文章對你有幫助,若是對你有幫助請點個推薦,感謝!