Langue source:La connexion hyperlientérée est visible.
Résumé: Même si vous pensez savoir ce que vous faites, il n’est pas sûr de stocker quoi que ce soit dans un stockage ThreadStatic member, CallContext ou Thread Local dans une application ASP.Net si la valeur a pu être définie à l’avance. à Page_Load (par exemple dans IHttpModule ou constructeur de pages), mais pendant ou après l’accès.
[Mise à jour : août 2008 Étant donné que pas mal de personnes continuent de mettre des liens vers cet article, je pense qu’il est nécessaire de préciser que ce comportement de changement de fil se produit à un moment très précis du cycle de vie de la page, et non quand cela le souhaite.] Ma formulation après avoir cité Jeff Newsom est regrettable. À part cela, je suis très heureux (et flatté) de voir plusieurs fois des références à cet article dans les discussions de conception sur la gestion correcte d’HttpContext. Je suis content que les gens trouvent ça utile. ]
Il y a beaucoup de confusion sur la manière d’implémenter des singletons spécifiques à chaque utilisateur dans ASP.Net – c’est-à-dire que les données globales ne sont globales que pour un seul utilisateur ou une requête. Ce n’est pas une exigence rare : publier les données transactionnelles, de contexte de sécurité ou autres données « globales » en un seul endroit, plutôt que de les pousser dans chaque appel de méthode, car les données loam permettent une implémentation plus claire (et plus lisible). Cependant, si vous n’êtes pas prudent, c’est un excellent endroit pour vous tirer une balle dans le pied (ou la tête). Je pensais savoir ce qui se passait, mais ce n’était pas le cas.
L’option préférée sera le singleStocké dans HttpContext.Current.Items, simple et sécurisé, mais reliant le singleton en question à son utilisation dans la ASP.Net application. Si le singleton échoue dans votre objectif commercial, ce n’est pas idéal. Même si vous enveloppez l’accès à la propriété dans une instruction if
Summary: Même si vous pensez savoir ce que vous faites, il n’est pas sûr de stocker quoi que ce soit dans un membre ThreadStatic, un CallContext ou un stockage local Thread au sein d’une application ASP.Net, s’il est possible que la valeur peut être configurée avant Page_Load (par exemple dans IHttpModule ou constructeur de pages) mais accessible pendant ou après.
[Mise à jour : août 2008 Compte tenu du nombre assez important de personnes qui continuent de lier ce post, je ressens le besoin de préciser que ce comportement d’échange de fils se produit à un moment très précis de la page Cycle de vie et pas quand ça en a l’air. Ma formulation après la citation de Jef Newson était malheureuse. Cela mis à part, j’ai été immensément satisfait (et flatté) par le nombre de fois où j’ai vu ce post cité dans des discussions de conception sur la gestion appropriée de HttpContext. Je suis content que les gens l’aient trouvé utile.]
Il y a beaucoup de confusion quant à la manière d’implémenter des singletons spécifiques à chaque utilisateur dans ASP.Net – c’est-à-dire des données globales qui ne sont globales que pour un utilisateur ou une requête. Ce n’est pas une exigence rare : publier des transactions, du contexte de sécurité ou d’autres données « globales » en un seul endroit, plutôt que de les transmettre à chaque appel de méthode comme les données tramp peuvent en faire un une implémentation plus propre (et plus lisible). Cependant, c’est un excellent endroit pour se tirer une balle dans le pied (ou la tête) si on n’y fait pas attention. Je pensais savoir ce qui se passait, mais ce n’était pas le cas.
L’option privilégiée, qui consiste à stocker vos singletons dans HttpContext.Current.Items, est simple et sûre, mais elle lie le singleton en question à une utilisation dans une application ASP.Net. Si le singleton est en baisse dans vos objets métier, ce n’est pas idéal. Même si vous enveloppez l’access-propriété dans une instruction if
... alors il faut toujours référencer System.Web depuis cet assemblage, qui a tendance à placer plus d’objets « webby » au mauvais endroit.
Les alternatives sont d’utiliser un membre statique [ThreadStatic], un stockage local Thread (qui revient à peu près à la même chose), ou CallContext.
Les problèmes avec [ThreadStatic] sont bien documentés, mais pour résumer : Les initiateurs de champ ne s’activent que sur le premier fil Les données ThreadStatic nécessitent un nettoyage explicite (par exemple dans EndRequest), car même si le Thread est atteignable, les données ThreadStatic ne seront pas GC, donc vous pourriez fuir des ressources. Les données ThreadStatic ne sont valables que dans une requête, car la requête suivante peut arriver sur un autre thread et obtenir les données de quelqu’un d’autre. Scott Hanselman a raison, ThreadStatic ne s’entend pas bien avec ASP.Net, mais n’explique pas entièrement pourquoi.
Le stockage dans CallContext atténue certains de ces problèmes, puisque le contexte disparaît à la fin de la requête et que la validation générale finira par survenir (même si vous pouvez toujours fuir des ressources jusqu’à ce que la vérification générale se produise si tu stockes des jetables). De plus, CallContext est la façon dont HttpContext est stocké, donc ça doit être correct, non ? Quoi qu’il en soit, on pourrait penser (comme moi) que si vous nettoyez derrière vous à la fin de chaque demande, tout irait bien : « Si vous initialisez la variable ThreadStatic au début de la requête et gérez correctement l’objet référencé à la fin de la requête, je risquerais de prétendre qu’il ne se passera rien de mauvais
« Maintenant,Je peux me tromper. CLR peut arrêter d’héberger un thread à mi-chemin, sérialiser sa pile quelque part, lui donner une nouvelle pile, et la laisser commencer à s’exécuter。 J’en doute profondément. Je suppose qu’il est concevable que l’hyperthreading compliquerait aussi les choses, mais j’en doute aussi. ”
Jeff Newsom
« Si vous initialisez une variable ThreadStatic au début d’une requête, et que vous éliminez correctement l’objet référencé à la fin de la requête, je vais prendre un risque et affirmer que rien n’est Le mal arrivera. Vous êtes même cool entre les contextes dans le même domaine d’application
« Je peux me tromper. Le clr pourrait potentiellement arrêter un thread géré en cours de route, sérialiser sa pile quelque part, lui donner une nouvelle pile, et la laisser commencer à exécuter. J’en doute vraiment. Je suppose qu’il est concevable que l’hyperthreading complique aussi les choses, mais j’en doute aussi. » Jef Newsom Mise à jour : C’est trompeur. J’expliquerai plus tard que ce changement de fil ne peut se faire qu’entre BeginRequest et Page_Load, mais la référence de Jef crée une image très puissante que je n’ai pas réussi à corriger immédiatement.
Update: This was the misleading bit. I do explain further later on that this thread-swap can only happen between the BeginRequest and the Page_Load, but Jef's quote creates a very powerful image I failed to immediately correct. My bad. Donc, à un moment donné, ASP.NET décide qu’il y a trop de fils d’E/S traitant d’autres requêtes. […] Il n’accepte que les requêtes et les met en file d’attente dans un objet de file interne au ASP.NET runtime. Ensuite, après la mise en file, le fil d’E/S demande un fil de travail, puis le fil d’E/S revient dans son pool. […] Par conséquent, ASP.NET laissera ce travailleur gérer la demande. Il l’amènera à ASP.NET runtime, comme le thread d’E/S le ferait en cas de faible charge.
À un moment donné, ASP.NET décide qu’il y a trop de fils d’E/S qui traitent d’autres requêtes. [...] Il prend simplement la requête et la met en file d’attente dans cet objet interne au ASP.NET runtime. Ensuite, une fois que cela est mis en file, le fil d’E/S demandera un thread de travail, puis le thread d’E/S sera renvoyé à son pool. [...] Donc ASP.NET demandera à ce fil de travail de traiter la demande. Il l’emmènera dans le temps d’exécution ASP.NET, tout comme le thread d’E/S le ferait sous faible charge. Je l’ai toujours su, mais je pense que c’est arrivé très tôt et je m’en fichais. Cependant, je semble me tromper. Nous avons eu un problème dans ASP.Net’application où un utilisateur cliquait sur un lien après avoir cliqué sur un autre lien, et notre application avait une exception de référence nulle dans l’un des singletons (j’ai utilisé CallContext au lieu de ThreadStatic pour le singleton, mais cela s’est avéré sans importance).
J’ai fait quelques recherches sur le fonctionnement exact des ASP.Net threads, et j’ai obtenu des avis contradictoires déguisés en faits (les requêtes sont agiles dans les threads, tandis que les requêtes sont ancrées sur des threads pendant leur durée de vie), donc j’ai copié mon problème dans une application test avec une page lente (en veille une seconde) et une page rapide. Je clique sur le lien vers la page lente, et avant que la page ne revienne, je clique sur le lien vers la page rapide. Le résultat (le dump log4net de ce qui se passe) m’a époustouflé.
La sortie montre que pour la seconde requête, l’événement BeginRequest et le constructeur de page dans le pipeline HttpModule s’activent sur un thread, mais page_Load dans un autre. Le second thread a déjà migré l’HttpContext du premier thread, mais pas le CallContext ni le ThreadStatic (note : puisque l’HttpContext lui-même est stocké dans le CallContext, cela signifie que ASP.Net migre explicitement l’HttpContext). Répétons dis-le :
J’ai toujours su cela, mais j’ai supposé que c’était arrivé assez tôt dans le processus pour que je m’en fiche. Il semble cependant que je me sois trompé. Nous avons eu un problème dans notre application ASP.Net où l’utilisateur clique sur un lien juste après en avoir cliqué sur un autre, et notre application explose avec une exception de référence nulle pour l’un de nos singletons (j’utilise CallContext et non ThreadStatic pour le singleton, mais il s’avère que ça n’a pas d’importance).
J’ai fait quelques recherches sur la façon exacte de ASP. Le threading de net fonctionne, et il y a des opinions contradictoires — déguisées en faits (les requêtes sont agiles dans un thread dans une requête versus les requêtes sont épinglées sur un fil pendant toute leur durée de vie), donc j’ai reproduit mon Problème dans une application test avec une page lente (veille une seconde) et une page rapide. Je clique sur le lien de la page lente et avant que la page ne revienne, je clique sur le lien de la page rapide. Les résultats (un dump log4net de ce qui se passe) m’ont surpris.
Ce que montre la sortie, c’est que - pour la deuxième requête - les événements BeginRequest dans le pipeline HttpModule et le constructeur de page s’activent sur un thread, mais le Page_Load s’active sur un autre. Le second thread a vu l’HttpContext migré du premier, mais pas le CallContext ni le ThreadStatic (NB : puisque HttpContext est lui-même stocké dans CallContext, cela signifie ASP.Net est migrant explicitement l’HttpContext à l’horizontale). Expliquons cela encore une fois :
- Le changement de thread a lieu après la création de l’IHttpHandler
- Après l’initialisation de champs et l’exécution du constructeur de la page
- Après tout événement de type BeginRequest, AuthenticateRequest, AquireSessionState utilisé par Global.ASA/IHttpModules.
- Seul l’HttpContext est migré vers le nouveau fil
Le commutateur de thread a lieu après la création de l’IHttpHandler Après que les initialiseurs de champs et le constructeur de la page soient exécutés Après tous les événements de type BeginRequest, AuthenticateRequest, AquireSessionState utilisés par vos modules Global.ASA / IHttp. Seul l’HttpContext migre vers le nouveau fil de discussion C’est un vrai casse-tête car, d’après ce que j’ai vu, cela signifie que la seule option de persistance pour le comportement de la classe « ThreadStatic » dans ASP.Net est d’utiliser HttpContext. Donc, pour vos objets métier, soit vous continuez à utiliser if(HttpContext.Current !). =null) et la référence System.Web (beurk), ou il faut inventer un modèle de fournisseur pour la persistance statique, qui doit être configuré avant d’accéder à ces singletons. Double nausée.
Veuillez dire que ce n’est pas le cas.
Annexe : Journal complet :
C’est vraiment pénible, car d’après ce que je vois, la seule option de persistance pour un comportement à la « ThreadStatic » dans ASP.Net est d’utiliser HttpContext. Donc, pour vos objets métier, soit vous êtes coincé avec if(HttpContext.Current !=null) et la référence System.Web (beurk), soit vous devez inventer un modèle de fournisseur pour votre persistance statique, qui devra être configurée avant que l’un de ces singletons ne soit accédé. Double dégueu.
S’il vous plaît, quelqu’un dis-le contraire.
Appendix: That log in full: [3748] INFO 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/SlowPage.aspx [3748] INFO 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(now)=97, calldata= [3748] INFO 11:10:05,249 ASP. SlowPage_aspx.. ctor() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748 [3748] INFO 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748 [3748] INFO 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - Mise en veille lente des pages....
[2720] INFO 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/FastPage.aspx [2720] INFO 11:10:05,679 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(now)=1835, calldata= [2720] INFO 11:10:05,679 ASP. FastPage_aspx.. ctor() - threadid=2720, threadhash=(cctor)1835, threadhash(maintenant)=1835, calldata=2720, logicalcalldata=2720, threadstatic=2720
[3748] INFO 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Page qui se réveille lentement.... [3748] INFO 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748 [3748] INFO 11:10:06,350 ASP. Global_asax. Application_EndRequest() - threadid=3748, threadhash=97, threadhash(maintenant)=97, calldata=3748 [3748] INFO 11:10:06,350 ASP. Global_asax. Application_EndRequest() - FIN /ConcurrentRequestsDemo/SlowPage.aspx
[4748] INFO 11:10:06,791 ASP. FastPage_aspx. Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(now)=1703, calldata=, logicalcalldata=, threadstatic= [4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(maintenant)=1703, calldata= [4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - FIN /ConcurrentRequestsDemo/FastPage.aspx L’essentiel est ce qui se passe lorsque le Page_Load de FastPage est déclenché. Le ThreadID est 4748, mais le ThreadID que j’ai stocké dans le HttpContext de ctor est 2720. Le code de hachage pour les threads logiques est 1703, mais le code de hachage que je stocke dans le ctor est 1835. Toutes les données que j’ai stockées dans le CallContext manquent (même les données marquées ILogicalThreadAffinnative), mais l’HttpContext est toujours là. Comme on pouvait s’y attendre, mon ThreadStatic a aussi disparu.
L’élément clé est ce qui se passe lorsque le Page_Load de FastPage s’active. Le ThreadID est 4748, mais l’identifiant de thread que j’ai stocké dans HttpContext dans le ctor est 2720. Le code de hachage du thread logique est 1703, mais celui que j’ai stocké dans le ctor est 1835. Toutes les données que j’ai stockées dans le CallContext ont disparu (même celles marquées ILogicalThreadAffinative), mais HttpContext est toujours là. Comme on pouvait s’y attendre, mon ThreadStatic a aussi disparu. (Fin)
|