66 votes

Pourquoi utiliser des requêtes asynchrones au lieu d'utiliser un pool de threads plus important?

Pendant les Techdays ici aux pays-bas Steve Sanderson a donné une présentation sur C#5, ASP.NET MVC 4, et Web asynchrone.

Il a expliqué que, lorsque des demandes de prendre du temps à la fin, tous les threads du pool de threads deviennent occupés et de nouvelles demandes à attendre. Le serveur ne peut pas gérer la charge et tout ralentit.

Il a ensuite montré comment l'utilisation de async webrequests améliore les performances car le travail est alors déléguée à un autre thread et le pool de threads peut répondre rapidement aux nouvelles demandes entrantes. Il a même fait des démos et a montré que 50 demandes simultanées d'abord pris 50 * 1s, mais avec le comportement asynchrone en place à seulement 1,2 s au total.

Mais après avoir vu ce j'ai encore quelques questions.

  1. Pourquoi ne peut-on pas utiliser un plus gros pool de threads? N'utilise pas async/await pour afficher un autre thread plus lent juste d'augmenter le pool de threads depuis le début? Ce n'est pas comme le serveur nous courir soudainement obtenir plus de threads ou de quelque chose?

  2. La demande de l'utilisateur est toujours en attente de la async fil à la fin. Si le fil de la piscine est en train de faire quelque chose d'autre, comment le " UI " thread occupé? Steve a dit quelque chose à propos de 'smart noyau qui sait quand quelque chose est fini". Comment cela fonctionne?

63voto

Flavien Points 1101

C'est une très bonne question, et la compréhension est la clé pour comprendre pourquoi asynchrone IO est si important. La raison pour laquelle la nouvelle async/await fonctionnalité a été ajoutée à C# 5.0 est de simplifier l'écriture de code asynchrone. Soutien pour le traitement asynchrone sur le serveur est cependant pas nouveau, il existe depuis ASP.NET 2.0.

Comme Steve vous a montré, avec un traitement synchrone, chaque demande ASP.NET (WCF) prend un fil de la piscine. La question qu'il fait des démos est une question bien connue appelée "pool de threads de la famine". Si vous faites synchrone IO sur votre serveur, le thread du pool restera bloquée (ne rien faire) pour la durée de l'OI. Depuis il y a une limite dans le nombre de threads dans le pool de threads, sous la charge, ce qui peut conduire à une situation où tous les threads du pool de threads sont bloqués en attente d'e / s, et la demande commence à être mis en file d'attente, ce qui provoque une augmentation de temps de réponse. Depuis, tous les threads sont en attente d'une e / s, vous verrez un CPU d'occupation proche de 0% (même si les temps de réponse de passer par le toit).

Ce que vous demandez (Pourquoi ne peut-on pas utiliser un plus gros pool de threads?) est une très bonne question. Comme une question de fait, c'est comment la plupart des gens ont été de résoudre le problème de pool de threads de la famine jusqu'à présent: plus de threads sur le pool de threads. Une partie de la documentation de Microsoft indique même qu'un correctif pour les situations où le pool de thread la famine peut se produire. C'est une solution acceptable, et jusqu'à C# 5.0, il était beaucoup plus facile à faire, que de réécrire votre code pour être totalement asynchrone.

Il y a quelques problèmes avec l'approche:

  • Il n'y a pas de valeur qui fonctionne dans toutes les situations: le nombre de threads du pool de threads que vous allez avoir besoin dépend linéairement sur la durée de l'OI, et la charge sur votre serveur. Malheureusement, IO latence est la plupart du temps imprévisibles. Voici un exemple: Disons que vous faire des requêtes HTTP à un tiers de service web dans votre ASP.NET l'application, qui prend environ 2 secondes. Vous rencontrez pool de threads de la famine, si vous décidez d'augmenter la taille du pool de threads, disons, 200 fils, et puis il commence à travailler à nouveau. Le problème, c'est que peut-être la semaine prochaine, le service web de problèmes techniques, ce qui augmente leur temps de réponse de 10 secondes. Tout d'un coup, le pool de thread la famine est de retour, parce que les threads sont bloqués à 5 fois plus longtemps, vous avez maintenant besoin d'augmenter le nombre 5 fois, à 1000 fils.

  • L'évolutivité et les performances: Le deuxième problème est que si vous faites cela, vous pourrez toujours utiliser un thread par demande. Les Threads sont une ressource coûteuse. Chaque thread géré .NET requiert l'un de l'allocation de mémoire de 1 MO pour la pile. Pour une page web faisant IO qui durent 5 secondes, et avec une charge de 500 requêtes par seconde, vous aurez besoin de 2 500 threads dans votre pool de threads, ce qui signifie 2,5 GO de mémoire pour les piles de threads qui sera assis à ne rien faire. Vous avez alors le problème de la commutation de contexte, qui va prendre un lourd tribut sur les performances de votre machine (touchant tous les services sur la machine, et pas seulement votre application web). Même si Windows fait un assez bon travail à ignorer les threads en attente, il n'est pas conçu pour supporter un grand nombre de threads. Rappelez-vous que le rendement le plus élevé est obtenu lorsque le nombre de threads en cours d'exécution est égal au nombre de Processeurs logiques sur la machine (généralement pas plus de 16 ans).

L'augmentation de la taille du pool de threads est une solution, et les gens l'ont fait que pour une dizaine d'années (même dans les propres produits de Microsoft), il est un peu moins évolutif et efficace, en termes de mémoire et d'utilisation de l'UC, et vous êtes toujours à la merci d'une hausse soudaine de IO latence qui serait la cause de la famine. Jusqu'en C# 5.0, la complexité du code asynchrone n'était pas la peine pour de nombreuses personnes. async/await change tout comme maintenant, vous pouvez profiter de l'évolutivité de l'asynchrone IO, et d'écrire du code simple, dans le même temps.

Plus de détails: http://msdn.microsoft.com/en-us/library/ff647787.aspx "Utiliser des appels asynchrones à invoquer des services Web ou des objets à distance quand il y a la possibilité d'effectuer un parallèle supplémentaire de traitement alors que l'appel de service Web produit. Si possible, éviter synchrone (blocage) des appels à des services Web, car sortant des appels de service Web sont fabriqués en utilisant des fils de l'ASP.NET pool de threads. Blocage des appels de réduire le nombre de threads disponibles pour le traitement des autres demandes entrantes."

31voto

Stephen Cleary Points 91731
  1. Async/await n'est pas basé sur des fils; il est basé sur le traitement asynchrone. Lorsque vous effectuez une asynchrones attendre dans ASP.NET le thread de demande est renvoyée au pool de threads, donc il n'ya pas de fils de l'entretien de cette demande jusqu'à ce que l'opération asynchrone est terminée. Depuis la demande de charge est inférieur que de fil de frais généraux, cela signifie async/await pouvez mettre à l'échelle de mieux que le pool de threads.
  2. La demande a un décompte de l'encours des opérations asynchrones. Ce compte est géré par la ASP.NET la mise en œuvre de l' SynchronizationContext. Vous pouvez en lire plus à propos de SynchronizationContext dans mon article MSDN - elle porte sur la façon ASP.NET' SynchronizationContext fonctionne et comment l' await utilise SynchronizationContext.

ASP.NET le traitement asynchrone a été possible avant async/await - vous pouvez utiliser les pages asynchrones, et l'utilisation du PAE composants tels que WebClient (basé sur des Événements de la Programmation Asynchrone est un style de programmation asynchrone basé sur SynchronizationContext). Async/await utilise également SynchronizationContext, mais a un beaucoup plus facile de syntaxe.

8voto

chakrapani bhat Points 33

Imaginez le pool de threads comme un ensemble de travailleurs que vous avez employé pour faire votre travail. Vos travailleurs course rapide cpu instructions de votre code.

Maintenant, votre travail se fait de dépendre d'une autre lent type de travail; la lenteur des gars le disque ou sur le réseau. Par exemple, votre travail peut comporter deux parties, une partie qui a pour exécuter avant la lente type de travail, et une partie qui a pour exécuter après la lente type de travail.

Comment auriez-vous des conseils à vos employés de faire votre travail? Voudriez-vous dire à chaque travailleur - "Faire de cette première partie, puis attendez jusqu'à ce que la lenteur des gars est fait, et ensuite faire votre deuxième partie" ? Voulez-vous augmenter le nombre de vos travailleurs, parce que tous d'entre eux semblent être en attente pour que la lenteur des gars et que vous n'êtes pas en mesure de satisfaire de nouveaux clients? Non!

Vous serait plutôt de demander à chaque travailleur de faire la première partie et demander à la lenteur de guy de revenir et de déposer un message dans une file d'attente lorsque vous avez terminé. Vous dites que chaque travailleur (ou peut-être un dédié sous-ensemble des travailleurs) de regarder pour faire des messages dans la file d'attente et de faire la deuxième partie de ce travail.

La smart noyau vous allusion ci-dessus est le système d'exploitation, la capacité de maintenir une telle file d'attente pour la lenteur du disque et du réseau IO achèvement des messages.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X