2014-07-17 4 views
2

J'ai des problèmes pour utiliser HttpClient dans plusieurs threads..NET HttpClient multithread

Si je lance de nombreux téléchargement en même temps, le premier téléchargement de chaque fil est très lent (et augmentent avec des fils parallèles)

Par exemple, si j'ai un fil, tout va bien

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

mais avec un fil, le temps de téléchargement augmenter

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

réseau n'est pas une bande passante problème, j'ai même problème avec localhost.

Voici un code pour reproduire le problème:

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 - 'Console' est threadsafe, et n'est pas inclus dans les timings. –

+0

@BrianReischl mon mauvais! –

+1

Il peut être lié (ou non), mais la convention observée par .net est que seules trois connexions HTTP à la même URL sont autorisées à partir d'un système d'exploitation client particulier. Ceci est défini dans ServicePointManager.DefaultConnectionLimit. Ceci est discuté ici http://stackoverflow.com/questions/866350/how-can-i-programmatically-remove-the-2-connection-limit-in-webclient et peut vous amener à observer un ralentissement spectaculaire des activités http parallèles sur la même URL. – PhillipH

Répondre

1

Je ne peux pas reproduire réellement votre problème sur ma machine. Peu importe ce que je fais, la première demande prend toujours à peu près le même temps, et les secondes demandes sont plus rapides.

Ma meilleure estimation est que vous utilisez le pool de threads. Chaque fois que vous appelez Task.Factory.StartNew, il démarre un nouveau Task, qui prend un nouveau thread threadpool. Ensuite, vous appelez HttpClient.GetAsync, qui commence un autre Task sur un autre thread et bloque le premier thread. Donc, vous prenez deux threads pour chaque demande. Si vous faites cela assez, vous utiliserez tous les threads dans le pool de threads, et les requêtes commenceront à faire la queue. Le pool de threads ajoutera plus de threads, mais lentement - généralement un nouveau thread toutes les 0,5 secondes. Donc, cela pourrait en faire partie. En outre, vous utilisez HttpClient incorrect. Chaque instance HttpClient contient un pool de connexions, de sorte que vous souhaitiez généralement créer une instance et la réutiliser. Voici un code plus propre - essayez-le et voyez s'il résout votre problème.

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

Votre solution fonctionne, mais je ne peux pas le faire. J'ai vraiment besoin d'avoir plusieurs threads, qui lancent des téléchargements. Pour être plus précis, je vais avoir 10s de longs threads en cours d'exécution, l'écoute des files d'attente MSMQ, qui lancent le téléchargement lors de la réception des messages. –

+0

Ajout de ThreadPool.SetMinThreads (100, 100); rend le temps d'exécution beaucoup mieux. Le nombre minimum de threads pour un comportement correct est number_tasks + 1 –

+0

Mon code utilise des threads, il utilise moins de threads plus efficacement. Si vous l'exécutez, vous verrez que les exécutent en parallèle. Sous les couvertures, le framework commencera chaque requête, et quand il se termine, un des threads d'achèvement d'E/S du pool de threads le servira. C'est beaucoup plus efficace que d'attacher un fil pour chaque demande. J'ai effectivement fait les tests moi-même, et utiliser async de cette façon est un peu plus évolutif, et des charges plus rapides et plus élevées. –