Utilisation de ThreadPool.QueueUserWorkItem dans ASP.NET dans un scénario de trafic intense
J'ai toujours eu l'impression que l'utilisation de ThreadPool pour des tâches d'arrière-plan éphémères (disons non critiques) était considérée comme une pratique exemplaire, même dans ASP.NET, mais je suis ensuite tombé sur cet article qui semble suggérer le contraire - l'argument étant que vous devez quitter le ThreadPool pour traiter les demandes liées à ASP.NET.
Alors voici comment j'ai fait de petites tâches asynchrones jusqu'à présent:
ThreadPool.QueueUserWorkItem(s => PostLog(logEvent))
Et l'article suggère plutôt de créer un fil explicitement, semblable à:
new Thread(() => PostLog(logEvent)){ IsBackground = true }.Start()
La première méthode a l'avantage d'être gérée et liée, mais il existe un risque (si l'article est correct) que les tâches en arrière-plan se disputent les threads avec les gestionnaires de demandes ASP.NET. La deuxième méthode libère le ThreadPool, mais au prix d’être illimitée et donc d’exploiter potentiellement trop de ressources.
Ma question est donc la suivante: les conseils de l'article sont-ils corrects?
Si votre site recevait autant de trafic que votre ThreadPool était saturé, est-il préférable de sortir du groupe ou serait-il préférable qu'un ThreadPool complet implique de toute façon que vous atteignez la limite de vos ressources, auquel cas vous ne devrait pas essayer de démarrer vos propres discussions?
Précision: je demande simplement, dans le cadre de petites tâches asynchrones non critiques (par exemple, la journalisation à distance), pas d’éléments de travail coûteux qui nécessiteraient un processus séparé (dans ce cas, je conviens que vous aurez besoin d’une solution plus robuste).
D'autres réponses ici semblent laisser de côté le point le plus important:
Si vous n'essayez pas de paralléliser une opération gourmande en ressources processeur afin de l'exécuter plus rapidement sur un site à faible charge, il est inutile d'utiliser un thread de travail.
Cela vaut pour les deux threads libres, créés par new Thread(...)
, et les threads de travail dans ThreadPool
qui répondent aux demandes QueueUserWorkItem
.
Oui, c’est vrai, vous pouvez affamer la ThreadPool
dans un processus ASP.NET en mettant en file d’attente trop d’éléments de travail. Cela empêchera ASP.NET de traiter d'autres requêtes. Les informations contenues dans l'article sont exactes à cet égard. le même pool de threads que celui utilisé pour QueueUserWorkItem
est également utilisé pour répondre aux demandes.
Mais si vous mettez en file d'attente suffisamment d'éléments de travail pour provoquer cette famine, vous devriez affamer le pool de threads! Si vous exécutez des centaines d'opérations gourmandes en ressources en même temps, à quoi servirait-il qu'un autre thread de travail réponde à une demande ASP.NET, alors que la machine est déjà surchargée? Si vous rencontrez cette situation, vous devez complètement repenser votre conception!
La plupart du temps, je vois ou j'entends dire que le code multithread est utilisé de manière inappropriée dans ASP.NET. Il ne s'agit pas de mettre en file d'attente un travail gourmand en ressources processeur. C'est pour mettre en file d'attente le travail lié aux entrées/sorties. Et si vous voulez faire du travail d’E/S, vous devriez utiliser un thread d’E/S (Port d’achèvement d’E/S).
Plus précisément, vous devriez utiliser les rappels asynchrones pris en charge par la classe de bibliothèque que vous utilisez. Ces méthodes sont toujours très clairement étiquetées; ils commencent par les mots Begin
et End
. Comme dans Stream.BeginRead
, Socket.BeginConnect
, WebRequest.BeginGetResponse
et ainsi de suite.
Ces méthodes do utilisent la variable ThreadPool
, mais elles utilisent des IOCP, qui n'interfèrent pas avec les requêtes ASP.NET. Ils constituent un type particulier de thread léger pouvant être "réveillé" par un signal d'interruption du système d'E/S. Et dans une application ASP.NET, vous avez normalement un thread d'E/S pour chaque thread de travail, de sorte que chaque requête peut avoir une opération asynchrone mise en file d'attente. Cela représente littéralement des centaines d'opérations asynchrones sans dégradation significative des performances (en supposant que le sous-système d'E/S puisse suivre). C'est bien plus que ce dont vous aurez besoin.
Gardez simplement à l'esprit que les délégués asynchrones ne fonctionnent pas de cette façon - ils finiront par utiliser un thread de travail, tout comme ThreadPool.QueueUserWorkItem
. Seules les méthodes asynchrones intégrées aux classes de la bibliothèque .NET Framework sont capables de le faire. Vous pouvez le faire vous-même, mais c'est compliqué et un peu dangereux et dépasse probablement le cadre de cette discussion.
La meilleure réponse à cette question, à mon avis, est n'utilisez pas l'instance ThreadPool
ou une instance Thread
d'arrière-plan dans ASP.NET. Ce n'est pas du tout comme si on créait un thread dans une application Windows Forms, où vous le faites pour que l'interface utilisateur reste réactive et ne vous souciez pas de son efficacité. Dans ASP.NET, votre préoccupation est débit, et tout ce changement de contexte sur tous ces threads de travail va absolument tuer votre débit, que vous utilisiez la variable ThreadPool
ou non.
S'il vous plaît, si vous vous trouvez en train d'écrire du code de thread dans ASP.NET, déterminez si celui-ci peut être réécrit pour utiliser des méthodes asynchrones préexistantes et s'il ne le peut pas, alors veuillez vous demander si vous avez réellement besoin du code pour exécuter dans un fil de fond du tout. Dans la majorité des cas, vous ajouterez probablement de la complexité sans aucun avantage net.
Selon Thomas Marquadt de l’équipe ASP.NET de Microsoft, il est prudent d’utiliser le ThreadPool (QueueUserWorkItem) ASP.NET.
Q) Si mon application ASP.NET utilise des threads CLR ThreadPool, ne vais-je pas affamer ASP.NET, qui utilise également le CLR ThreadPool pour exécuter des demandes? Blockquote
A) [T] o résumer, ne vous inquiétez pas pour affamer ASP.NET des threads, et si vous pensez qu’il ya un problème ici, laissez Je sais et nous nous en occupons.
Q) Devrais-je créer mes propres threads (nouveau fil)? Cela ne va-t-il pas être mieux pour ASP.NET, car il utilise le CLR ThreadPool.
A) S'il vous plaît ne pas. Ou pour le dire un manière différente, non !!! Si vous êtes vraiment intelligent - beaucoup plus intelligent que moi - alors vous peut créer vos propres discussions; sinon, n'y pensez même pas. Voici quelques raisons pour lesquelles vous devriez pas fréquemment créer de nouveaux threads:
1) C'est très cher, comparé à QueueUserWorkItem ... Au fait, si vous pouvez écrire un meilleur ThreadPool que le CLR, je vous encourage à postuler à un emploi chez Microsoft, car nous recherchons des personnes comme vous!.
Je pense vraiment que la pratique générale pour un travail asynchrone rapide et de faible priorité dans ASP.NET consisterait à utiliser le pool de threads .NET, en particulier pour les scénarios à fort trafic, car vous souhaitez que vos ressources soient limitées.
En outre, l'implémentation du thread est masquée - si vous commencez à créer vos propres threads, vous devez également les gérer correctement. Ne dites pas que vous ne pouvez pas le faire, mais pourquoi réinventer cette roue?
Si les performances deviennent un problème et que vous pouvez établir que le pool de threads est le facteur limitant (et non les connexions à la base de données, les connexions réseau sortantes, la mémoire, les dépassements de délai de page, etc.), vous modifiez la configuration du pool de threads pour autoriser davantage de threads de travail et des demandes en file d'attente supérieures. , etc.
Si vous ne rencontrez pas de problème de performances, le choix de générer de nouveaux threads afin de réduire les conflits avec la file d'attente de demandes ASP.NET constitue une optimisation prématurée classique.
Dans l'idéal, vous ne devriez pas utiliser de thread distinct pour effectuer une opération de journalisation. Il suffit d'activer le thread d'origine pour terminer l'opération le plus rapidement possible. C'est là que MSMQ et un thread/processus consommateur distinct entrent en scène. Je conviens que cela est plus lourd et plus fastidieux à mettre en œuvre, mais vous avez vraiment besoin de la durabilité: la volatilité d’une file d’attente partagée en mémoire usera rapidement son accueil.
Les sites Web ne devraient pas contourner les fils de discussion.
Généralement, vous déplacez cette fonctionnalité dans un service Windows avec lequel vous communiquez (j'utilise MSMQ pour leur parler).
-- Modifier
J'ai décrit ici une implémentation: Traitement en arrière-plan basé sur une file d'attente dans une application Web ASP.NET MVC
-- Modifier
Pour développer pourquoi c'est encore mieux que de simples threads:
À l'aide de MSMQ, vous pouvez communiquer avec un autre serveur. Vous pouvez écrire dans une file d'attente sur plusieurs ordinateurs. Par conséquent, si vous déterminez, pour une raison quelconque, que votre tâche en arrière-plan utilise trop les ressources du serveur principal, vous pouvez simplement la déplacer de manière relativement simple.
Cela vous permet également de traiter par lots toutes les tâches que vous tentiez de faire (envoyer des courriels/peu importe).
Vous devez utiliser QueueUserWorkItem et éviter de créer de nouveaux threads comme vous le feriez pour éviter le fléau. Pour un visuel qui explique pourquoi vous ne mourrez pas de faim dans ASP.NET, puisqu'il utilise le même ThreadPool, imaginez un jongleur très habile qui utilise ses deux mains pour garder une demi-douzaine de quilles, épées ou autres objets en vol. Imaginez ce qui se passe à Seattle aux heures de pointe lorsque les rampes d'accès très utilisées de l'autoroute autorisent les véhicules à entrer immédiatement dans le trafic au lieu d'utiliser un feu et de limiter le nombre d'entrées à une toutes les quelques secondes. . Enfin, pour une explication détaillée, veuillez consulter ce lien:
Merci, Thomas
On m'a posé une question similaire au travail la semaine dernière et je vais vous donner la même réponse. Pourquoi utilisez-vous plusieurs applications Web multi-threading par demande? Un serveur Web est un système fantastique, fortement optimisé pour fournir de nombreuses demandes en temps voulu (par exemple, multi-threading). Pensez à ce qui se passe lorsque vous demandez presque toutes les pages du Web.
- Une demande est faite pour une page
- Html est rendu
- Le code HTML demande au client de faire d'autres requêtes (js, css, images, etc.)
- Des informations supplémentaires sont retournées
Vous donnez l'exemple de la journalisation à distance, mais cela devrait être une préoccupation de votre consignateur. Un processus asynchrone doit être en place pour recevoir les messages en temps voulu. Sam fait même remarquer que votre logger (log4net) devrait déjà le supporter.
Sam a également raison de dire que l'utilisation du pool de threads sur le CLR ne causera pas de problèmes avec le pool de threads dans IIS. La chose à considérer ici cependant, est que vous ne créez pas de threads à partir d'un processus, vous créez de nouveaux threads à partir de IIS threads de pool de threads. Il y a une différence et la distinction est importante.
Threads vs Process
Les threads et les processus sont des méthodes de paralléliser une application . Cependant, les processus sont indépendants unités d'exécution qui contiennent leurs propres informations d'état, utilisez leurs propres espaces d'adressage, et n'interagissez qu'avec mutuellement via interprocess mécanismes de communication (généralement gérés par le système d'exploitation) . Les applications sont généralement divisées dans les processus lors de la conception phase, et un processus maître explicitement engendre des sous-processus quand il fait sens de séparer logiquement fonctionnalité d'application significative . Les processus, en d'autres termes, sont un construction architecturale.
En revanche, un fil est un codage construction qui n'affecte pas le architecture d'une application. UNE un seul processus peut contenir plusieurs des fils; tous les threads d'un processus partager le même état et la même mémoire espace, et peut communiquer avec chaque directement, car ils partagent le mêmes variables.
Je dirais que l'article est faux. Si vous exploitez une grande boutique .NET, vous pouvez utiliser le pool en toute sécurité sur plusieurs applications et plusieurs sites Web (à l'aide de pools d'applications distincts), en vous basant simplement sur une instruction de la ThreadPool documentation:
Il y a un pool de threads par processus. Le pool de threads a une taille par défaut de 250 threads de travail par disponible processeur et achèvement de 1000 E/S les fils. Le nombre de threads dans le fichier Le pool de threads peut être modifié à l'aide de la méthode SetMaxThreads. Chaque fil utilise la taille de pile par défaut et exécute à la priorité par défaut.
Cet article n'est pas correct. ASP.NET dispose de son propre pool de threads, des threads de travail gérés, pour le traitement des demandes ASP.NET. Ce pool est généralement composé de quelques centaines de threads et est séparé du pool ThreadPool, qui est un multiple plus petit des processeurs.
L'utilisation de ThreadPool dans ASP.NET n'interférera pas avec les threads de travail ASP.NET. L'utilisation de ThreadPool est acceptable.
Il serait également acceptable de configurer un seul thread uniquement pour enregistrer les messages et utiliser un modèle de producteur/consommateur pour transmettre les messages de journalisation à ce thread. Dans ce cas, le thread ayant une longue durée de vie, vous devez créer un seul nouveau thread pour exécuter la journalisation.
Utiliser un nouveau fil pour chaque message est définitivement excessif.
Une autre alternative, si vous ne parlez que de journalisation, consiste à utiliser une bibliothèque telle que log4net. Il gère la connexion dans un thread distinct et prend en charge tous les problèmes de contexte pouvant survenir dans ce scénario.
Que IIS utilise ou non le même ThreadPool pour gérer les demandes entrantes semble difficile d'obtenir une réponse définitive, et semble également avoir changé de version. Il semblerait donc judicieux de ne pas utiliser excessivement les threads ThreadPool, de sorte que IIS en ait beaucoup disponibles. D'autre part, créer votre propre fil pour chaque petite tâche semble être une mauvaise idée. Vraisemblablement, vous avez une sorte de verrouillage dans votre journalisation, de sorte qu'un seul thread puisse progresser à la fois, et le reste ne ferait que se relayer pour être programmé et non programmé (sans parler de la surcharge de générer un nouveau thread). Essentiellement, vous rencontrez les problèmes exacts que le ThreadPool a été conçu pour éviter.
Il semble qu'un compromis raisonnable consisterait pour votre application à allouer un seul fil de journalisation auquel vous pourriez transmettre des messages. Vous voulez faire attention à ce que l'envoi de messages soit aussi rapide que possible afin de ne pas ralentir votre application.
Vous pouvez utiliser Parallel.For ou Parallel.ForEach et définir le nombre maximal de threads que vous souhaitez allouer pour s'exécuter sans à-coups et éviter ainsi la disette.
Cependant, étant exécuté en arrière-plan, vous devrez utiliser le style TPL pur ci-dessous dans l'application Web ASP.Net.
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
ParallelOptions po = new ParallelOptions();
po.CancellationToken = ts.Token;
po.MaxDegreeOfParallelism = 6; //limit here
Task.Factory.StartNew(()=>
{
Parallel.ForEach(collectionList, po, (collectionItem) =>
{
//Code Here PostLog(logEvent);
}
});
Je ne suis pas d'accord avec l'article cité en référence (C # feeds.com). Il est facile de créer un nouveau fil mais dangereux. Le nombre optimal de threads actifs à exécuter sur un seul cœur est en fait étonnamment faible - inférieur à 10. Il est bien trop facile de faire perdre du temps à la machine, si des threads sont créés pour des tâches mineures. Les threads sont une ressource qui nécessite une gestion. L'abstraction WorkItem est là pour gérer cela.
Il existe un compromis entre réduire le nombre de threads disponibles pour les demandes et créer trop de threads pour permettre à chacun d'entre eux de traiter efficacement. Il s’agit d’une situation très dynamique, mais qui devrait être gérée activement (dans ce cas, par le pool de threads) plutôt que de laisser au processeur le soin de rester en avance sur la création de threads.
Enfin, l’article décrit de manière assez radicale les dangers de l’utilisation du ThreadPool, mais il a vraiment besoin de quelque chose de concret pour les sauvegarder.