.net4.5中HttpClient使用註意點
.net4.5中的HttpClinet是個非常強大的類,但是在最近實際項目運用中發現了些很有意思的事情。
起初我是這樣用的:
using (var client = new HttpClient()) { }
但是發現相較於傳統的HttpRequest要慢上不少,後來查閱資料,發現HttpClient不應該每使用一次就釋放,因為socket是不能及時釋放的,需要把HttpClient作為靜態的來使用。
private static readonly HttpClient Client = new HttpClient();
再來後在使用過程當中需要密集的發送GET請求,但是總感覺慢,用fiddler查看,發現每次請求只並發了2次,代碼是用semaphoreSlim信號量來控制的,最大數量為10。而電腦的配置為r5 1600,系統為win7 x64,按照道理來說並發10是沒問題的,考慮到是否因為 ServicePointManager.DefaultConnectionLimit 限制了並發的數量,我修改了 ServicePointManager.DefaultConnectionLimit 的值為100,再次運行程序發現並發的數量還是2,於是上stackoverflow找到了這篇文章:
https://stackoverflow.com/questions/16194054/is-async-httpclient-from-net-4-5-a-bad-choice-for-intensive-load-applications
根據上面文章所講,似乎HttpClient是不遵守ServicePointManager.DefaultConnectionLimit的,並且在密集應用中HttpClient無論是準確性還是效率上面都是低於傳統意義上的多線程HttpRequest的。但是事實確實是這樣的嗎?如果真的是要比傳統的HttpRequest效率更為底下,那麽巨硬為什麽要創造HttpClient這個類呢?而且我們可以看到在上面鏈接中,提問者的代碼中HttpClient是消費了body的,而在HttpRequest中是沒有消費body的。帶著這樣的疑問我開始了測試。
var tasks = Enumerable.Range(1, 511).Select(async i => { await semaphoreSlim.WaitAsync(); try { var html = await Client.GetStringAsync($"http://www.fynas.com/ua/search?d=&b=&k=&page={i}"); var doc = parser.Parse(html); var tr = doc.QuerySelectorAll(".table-bordered tr:not(:first-child) td:nth-child(4)").ToList(); foreach (var element in tr) { list.Enqueue(element.TextContent.Trim()); } doc.Dispose(); } finally { semaphoreSlim.Release(); } });
上面這段代碼,是采集一個UserAgent大全的網站,而我的HttpClient及ServicePointManager.DefaultConnectionLimit是這樣定義的:
static Program() { ServicePointManager.DefaultConnectionLimit = 1000; } private static readonly HttpClient Client = new HttpClient(new HttpClientHandler(){CookieContainer = new CookieContainer()});
經過多次試驗,我發現,HttpClient是遵守了ServicePointManager.DefaultConnectionLimit的並發量的,默認還是2,大家仔細觀察一下不難發現其實HttpClient是優先於ServicePointManager.DefaultConnectionLimit設置的,也就是說HttpClient比ServicePointManager.DefaultConnectionLimit要先實例化,接下來我把代碼修改為這樣:
static Program() { ServicePointManager.DefaultConnectionLimit = 1000; Client = new HttpClient(new HttpClientHandler() { CookieContainer = new CookieContainer() }); } private static readonly HttpClient Client;
然後再次運行,打開fiddler進行監視,發現這個時候程序就能夠正常的進行並發10來訪問了。
而HttpClient中的HttpMessagehandle也是一個非常有趣的地方,我們可以進行實際的情況來進行包裝一下HttpMessageHandle,比如下面這段代碼實現了訪問失敗進行重試的功能:
public class MyHttpHandle : DelegatingHandler { public MyHttpHandle(HttpMessageHandler innerHandler):base(innerHandler) { } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { for (int i = 0; i < 2; i++) { var response = await base.SendAsync(request, cancellationToken); if (response.IsSuccessStatusCode) { return response; } else { await Task.Delay(1000, cancellationToken); } } return await base.SendAsync(request, cancellationToken); } }
在實例化HttpClient的時候,把我們定義的handle傳遞進去:
private static readonly HttpClient Client = new HttpClient(new MyHttpHandle(Handler))
這樣就實現了總共進行三次訪問,其中任意一次訪問成功就返回成功的結果,如果第二次訪問還沒成功就直接返回第三次訪問的結果。
.net4.5中HttpClient使用註意點