2014-07-17 45 views
2

我在多線程中使用HttpClient時遇到了一些麻煩。.NET HttpClient多線程

如果我同時推出很多下載,每個線程的第一下載速度很慢(與並行線程增加)

舉例來說,如果我有一個線程,一切都很好

First download Elapsed Time Download: 197 ms 
Second download Elapsed Time Download: 171 ms 

但有一個線程,下載時間增加

First download Elapsed Time Download: 3881 ms 
... 
Second download Elapsed Time Download: 96 ms 
... 

網絡帶寬是不是一個問題,我有同樣的問題localhost。

下面是一些代碼來重現問題:

static void Main(string[] args) 
{ 
    ServicePointManager.DefaultConnectionLimit = 200; 
    List<Task> tasks = new List<Task>(); 
    for (var i = 0; i < 10; i++) 
    { 
     tasks.Add(
      Task.Factory.StartNew(() => 
      { 
       Stopwatch s = Stopwatch.StartNew(); 
       HttpClient httpClient = new HttpClient(); 
       HttpResponseMessage httpDownloadResponse = httpDownloadResponse = httpClient.GetAsync("http://www.google.fr/", HttpCompletionOption.ResponseHeadersRead).Result; 
       s.Stop(); 
       Console.WriteLine("First download Elapsed Time Download: {0} ms", s.ElapsedMilliseconds); 

       s = Stopwatch.StartNew(); 
       httpClient = new HttpClient(); 
       httpDownloadResponse = httpClient.GetAsync("http://www.google.fr/", HttpCompletionOption.ResponseHeadersRead).Result; 
       s.Stop(); 
       Console.WriteLine("Second download Elapsed Time Download: {0} ms", s.ElapsedMilliseconds); 
      }) 
     ); 
    } 
    Task.WaitAll(tasks.ToArray()); 
    while (Console.ReadLine() != null) ; 
} 
+0

@Adriano - '控制檯'是線程安全的,並且不包括在計時中。 –

+0

@BrianReischl我的壞! –

+1

它可能是相關的(或可能不相關),但.net觀察到的約定是隻有三個到同一個URL的Http連接被允許來自特定的客戶端操作系統。這在ServicePointManager.DefaultConnectionLimit中定義。這是在這裏討論http://stackoverflow.com/questions/866350/how-can-i-programmatically-remove-the-2-connection-limit-in-webclient,並可能導致你觀察並行http活動戲劇性放緩在同一個網址上。 – PhillipH

回答

1

我實際上並不能在我的機器上重現您的問題。無論我做什麼,第一個請求總是需要大約相同的時間,第二個請求更快。

我最好的猜測是你正在用盡線程池。每次您撥打Task.Factory.StartNew時,它都會啓動一個新的Task,它將採用新的線程池線程。然後您撥打HttpClient.GetAsync,在另一個線程上啓動另一個Task並阻止第一個線程。所以你爲每個請求佔用兩個線程。如果你足夠的話,你將使用線程池中的所有線程,並且請求將開始排隊。線程池將添加更多的線程,但速度很慢 - 通常每0.5秒一個新線程。所以這可能是其中的一部分。

此外,您正在使用HttpClient錯誤。每個HttpClient實例都擁有一個連接池,因此您通常需要創建一個實例並重用它。這裏有一些更乾淨的代碼 - 試試看看它是否能解決你的問題。

public static void Main() 
{ 
    ServicePointManager.DefaultConnectionLimit = 200; 
    List<Task> tasks = new List<Task>(); 
    using (var client = new HttpClient()) 
    { 
     for (var i = 0; i < 10; i++) 
     { 
      tasks.Add(DoRequest(i, client)); 
     } 

     Task.WaitAll(tasks.ToArray()); 
    } 
} 

private async Task DoRequest(int id, HttpClient client) 
{ 
    const string url = "http://www.google.fr/"; 
    Stopwatch s = Stopwatch.StartNew(); 
    HttpResponseMessage httpDownloadResponse = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); 
    s.Stop(); 
    Console.WriteLine("Task {1} - First download Elapsed Time Download: {0} ms", s.ElapsedMilliseconds, id); 

    s = Stopwatch.StartNew(); 
    httpDownloadResponse = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); 
    s.Stop(); 
    Console.WriteLine("Task {1} - Second download Elapsed Time Download: {0} ms", s.ElapsedMilliseconds, id); 

} 
+0

您的解決方案有效,但我無法做到這一點。我真的需要有多個線程來啓動下載。更確切地說,我將有10個長時間運行的線程,監聽MSMQ隊列,在接收消息時啓動下載。 –

+0

添加ThreadPool.SetMinThreads(100,100);使執行時間更好。 Min線程的正確行爲是number_tasks + 1 –

+0

我的代碼使用線程,它只是更有效地使用更少的線程。如果你運行它,你會看到並行執行。在下面的框架中,框架將啓動每個請求,並且當它完成時,線程池中的一個I/O完成線程將爲它提供服務。這比爲每個請求綁定一個線程要高效得多。我實際上自己運行測試,並且以這種方式使用異步可擴展性更高,速度更快,負載更高。 –