1. 程式人生 > >.net4.5中HttpClient使用註意點

.net4.5中HttpClient使用註意點

測試 電腦 bsp code 大全 enqueue 是個 manage 正常的

  .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使用註意點