若是找的是core的HttpClientFactory 出門右轉。html
Dispose 不是立刻關閉tcp鏈接git
主動關閉的一方爲何不能立刻close而是進入timewait狀態:TCP四次揮手客戶端關閉連接爲何要等待2倍MSLgithub
正確寫法一個域(一個地址) 保證一個靜態httpclient操做,保證重用tcp鏈接。json
HttpClient有個接口SendAsync。看源碼知道其實HttpClient內部get,post,put,delete最終都是調用SendAsync。api
這個方法能夠容許用戶傳遞HttpRequestMessage,內部包含(HttpRequestHeaders)併發
關於Task同步上下文 形成死鎖問題就很少解釋。避免方法就是ConfigureAwait(false)或者await always。最好是await always。傳送門app
說下不用await 而使用相似HttpClient.GetStringAsync(uri).Result 直接同步獲取爲何沒有死鎖異步
由於HttpClient源碼裏面用到async await的地方几乎都加了ConfigureAwait(false)。233333async
其實這是嘴巴dudu園長大人在好久之前就作分析過 傳送門tcp
咱們通常的請求流程:發起請求,獲取返回的string對象,而後反序列化成咱們想要的對象。而其實能夠利用stream直接反序列化成咱們想要的對象。
並且能夠是在HttpCompletionOption.ResponseHeadersRead的狀況下。傳送門
封印:
/// <summary> /// 異步http請求者。 /// 注意:杜絕new的形式,能夠IOC容器單例注入 /// 若是多個address地址,經過name key形式注入多個單例 /// </summary> public class HttpAsyncSender { private readonly ILogger _logger = LoggerManager.GetLogger(typeof(HttpAsyncSender)); //靜態 重用tcp鏈接 長鏈接(not dispose)https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ private readonly HttpClient _httpClient = new HttpClient(); public HttpAsyncSender(Uri baseUri, int timeoutSeconds = 0, bool keepAlive = true, bool preheating = false, long maxResponseContentBufferSize = 0) { //基礎地址 _httpClient.BaseAddress = baseUri; //超時 if (timeoutSeconds != 0) { _httpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds); } //response最大接收字節 默認2gbmstsc if (maxResponseContentBufferSize != 0) { _httpClient.MaxResponseContentBufferSize = maxResponseContentBufferSize; } //長鏈接 //https://www.cnblogs.com/lori/p/7692152.html http 1.1 default set keep alive if (keepAlive) { _httpClient.DefaultRequestHeaders.Connection.Add("keep-alive"); } //httpclient 預熱 the first request //https://www.cnblogs.com/dudu/p/csharp-httpclient-attention.html if (preheating) { _httpClient.SendAsync(new HttpRequestMessage { Method = new HttpMethod("HEAD"), RequestUri = new Uri(baseUri + "/") }).Result.EnsureSuccessStatusCode(); } } #region 異步 /// <summary> /// GetAsync 注意 await always /// </summary> /// <param name="url"></param> /// <param name="cancellationToken"></param> /// <param name="jsonSerializerSettings"></param> /// <returns></returns> public async Task<T> GetAsync<T>(string url, CancellationToken cancellationToken, JsonSerializerSettings jsonSerializerSettings = null) { try { //https://johnthiriet.com/efficient-api-calls/ 減小內存開銷 利用stream特性 加快反序列化 using (var request = new HttpRequestMessage(HttpMethod.Get, url)) { var res = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var resStesam = await res.Content.ReadAsStreamAsync().ConfigureAwait(false); if (res.IsSuccessStatusCode) { return DeserializeJsonFromStream<T>(resStesam, jsonSerializerSettings); } var resStr = await StreamToStringAsync(resStesam).ConfigureAwait(false); _logger.Error($"HttpAsyncSender, GetAsync ,response fail StatusCode:{res.StatusCode} resStr:{resStr} BaseAddress:{_httpClient.BaseAddress},Url:{url}"); } } catch (AggregateException ae) { _logger.Error($"HttpAsyncSender,GetAsync AggregateException,BaseAddress:{_httpClient.BaseAddress},Url:{url} ae:{ae.Flatten()}"); throw; } catch (Exception e) { _logger.Error($"HttpAsyncSender,GetAsync Exception,BaseAddress:{_httpClient.BaseAddress},Url:{url}",e); throw; } return default(T); } /// <summary> /// PostAsync 注意 await always /// </summary> /// <param name="url"></param> /// <param name="content"></param> /// <param name="cancellationToken"></param> /// <param name="jsonSerializerSettings"></param> /// <returns></returns> public async Task<TRes> PostAsync<TReq, TRes>(string url, TReq content, CancellationToken cancellationToken, JsonSerializerSettings jsonSerializerSettings = null) { try { //https://johnthiriet.com/efficient-api-calls/ 減小內存開銷 利用stream特性 加快反序列化 using (var request = new HttpRequestMessage(HttpMethod.Post, url)) using (var httpContent = CreateHttpContent(content, jsonSerializerSettings)) { request.Content = httpContent; var res = await SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); var resStesam = await res.Content.ReadAsStreamAsync().ConfigureAwait(false); if (res.IsSuccessStatusCode) { return DeserializeJsonFromStream<TRes>(resStesam, jsonSerializerSettings); } var resStr = await StreamToStringAsync(resStesam).ConfigureAwait(false); _logger.Error($"HttpAsyncSender, PostAsync ,response fail StatusCode:{res.StatusCode} resStr:{resStr} BaseAddress:{_httpClient.BaseAddress},Url:{url}"); } } catch (AggregateException ae) { _logger.Error($"HttpAsyncSender,PostAsync AggregateException,BaseAddress:{_httpClient.BaseAddress},Url:{url} ae:{ae.Flatten()}"); throw; } catch (Exception e) { _logger.Error($"HttpAsyncSender,PostAsync Exception,BaseAddress:{_httpClient.BaseAddress},Url:{url}",e); throw; } return default(TRes); } /// <summary> /// SendAsync 注意 await always 當須要動態改變request head的時候 調用此方法。 解決 "集合已修改;可能沒法執行枚舉操做" /// </summary> /// <param name="httpRequestMessage"></param> /// <param name="completionOption"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage httpRequestMessage, HttpCompletionOption completionOption, CancellationToken cancellationToken) { try { return await _httpClient.SendAsync(httpRequestMessage, completionOption, cancellationToken).ConfigureAwait(false); } catch (AggregateException ae) { _logger.Error( $"HttpAsyncSender,SendAsync AggregateException,BaseAddress:{_httpClient.BaseAddress},Url:{httpRequestMessage.RequestUri} ae:{ae.Flatten()}"); throw; } catch (Exception e) { _logger.Error($"HttpAsyncSender,SendAsync Exception,BaseAddress:{_httpClient.BaseAddress},Url:{httpRequestMessage.RequestUri}",e); throw; } } #endregion private T DeserializeJsonFromStream<T>(Stream stream, JsonSerializerSettings jsonSerializerSettings = null) { if (stream == null || stream.CanRead == false) return default(T); using (var sr = new StreamReader(stream)) using (var jtr = new JsonTextReader(sr)) { var js = jsonSerializerSettings == null ? new JsonSerializer() : JsonSerializer.Create(jsonSerializerSettings); var searchResult = js.Deserialize<T>(jtr); return searchResult; } } private async Task<string> StreamToStringAsync(Stream stream) { string content = null; if (stream != null) using (var sr = new StreamReader(stream)) content = await sr.ReadToEndAsync(); return content; } public void SerializeJsonIntoStream(object value, Stream stream, JsonSerializerSettings jsonSerializerSettings = null) { using (var sw = new StreamWriter(stream, new UTF8Encoding(false), 1024, true)) using (var jtw = new JsonTextWriter(sw) { Formatting = Formatting.None }) { var js = jsonSerializerSettings==null?new JsonSerializer():JsonSerializer.Create(jsonSerializerSettings); js.Serialize(jtw, value); jtw.Flush(); } } private HttpContent CreateHttpContent<T>(T content, JsonSerializerSettings jsonSerializerSettings = null) { HttpContent httpContent = null; if (content != null) { var ms = new MemoryStream(); SerializeJsonIntoStream(content, ms, jsonSerializerSettings); ms.Seek(0, SeekOrigin.Begin); httpContent = new StreamContent(ms); httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } return httpContent; } }