Oprindelig:Hyperlink-login er synlig.
Resumé: Selv hvis du tror, du ved, hvad du laver, er det ikke sikkert at gemme noget i et ThreadStatic-medlem, CallContext eller Thread Local-lager i en ASP.Net-applikation, hvis værdien måske allerede er sat på forhånd. til at Page_Load (f.eks. i IHttpModule eller sidekonstruktør), men under eller efter adgang.
[Opdatering: august 2008 Da en del personer fortsat linker til denne artikel, føler jeg, det er nødvendigt at præcisere, at denne trådbytteadfærd sker på et meget specifikt tidspunkt i sidens livscyklus og ikke når det føles sådan.] Min formulering efter at have citeret Jeff Newsom er uheldig. Udover det er jeg meget glad (og beæret) over at se henvisninger til denne artikel flere gange i designdiskussioner om korrekt håndtering af HttpContext. Jeg er glad for, at folk finder det nyttigt. ]
Der er stor forvirring om, hvordan man implementerer brugerspecifikke singletons i ASP.Net – det vil sige, at globale data kun er globale for én bruger eller anmodning. Dette er ikke et usædvanligt krav: publicér transaktions-, sikkerhedskontekst- eller andre "globale" data ét sted i stedet for at presse det ind i hvert metodekald, fordi loam-data muliggør en klarere (og mere læsbar) implementering. Men hvis du ikke passer på, er dette et godt sted at skyde dig selv i foden (eller hovedet). Jeg troede, jeg vidste, hvad der foregik, men det gjorde jeg ikke.
Den foretrukne mulighed vil være singletonGemt i HttpContext.Current.Items, enkelt og sikkert, men relaterer den pågældende singleton til dens anvendelse i den ASP.Net anvendelse. Hvis singletonen fejler i dit forretningsobjekt, er det ikke ideelt. Selv hvis du pakker ejendomsadgang ind i en if-sætning
Summary: Selv hvis du tror, du ved, hvad du laver, er det ikke sikkert at gemme noget i et ThreadStatic-medlem, CallContext eller Thread Local Storage i en ASP.Net applikation, hvis der er mulighed for det at værdien kan være sat op før Page_Load (f.eks. i IHttpModule eller page constructor), men tilgås under eller efter.
[Opdatering: august 2008 I betragtning af det ret store antal personer, der fortsat linker til dette opslag, føler jeg behov for at præcisere, at denne trådbytteadfærd sker på et meget specifikt punkt på siden livscyklus og ikke når det føles som det. Min formulering efter Jef Newson-citatet var uheldig. Bortset fra det har jeg været enormt tilfreds (og beæret) over det antal gange, jeg har set dette indlæg nævnt i designdiskussioner om at håndtere HttpContext på passende vis. Jeg er glad for, at folk fandt det nyttigt.]
Der er meget forvirring om, hvordan man implementerer brugerspecifikke singletons i ASP.Net – altså globale data, der kun er globale for én bruger eller anmodning. Dette er ikke et usædvanligt krav: at publicere transaktioner, sikkerhedskontekst eller andre 'globale' data ét sted, i stedet for at sende det gennem alle metodekald, som tramp-data kan skabe en En renere (og mere læsbar) implementering. Men det er et godt sted at skyde sig selv i foden (eller hovedet), hvis man ikke er forsigtig. Jeg troede, jeg vidste, hvad der foregik, men det gjorde jeg ikke.
Den foretrukne løsning, hvor du gemmer dine singletons i HttpContext.Current.Items, er enkel og sikker, men binder singletonen til brug i en ASP.Net applikation. Hvis singlen er nede i dine forretningsobjekter, er det ikke ideelt. Selv hvis du pakker property-access ind i en if-sætning
... så skal du stadig referere System.Web fra den assembly, hvilket har tendens til at encoragere flere 'webby' objekter det forkerte sted.
Alternativerne er at bruge et [ThreadStatic] statisk medlem, Thread lokal lagring (som stort set svarer til det samme), eller CallContext.
Problemerne med [ThreadStatic] er veldokumenterede, men for at opsummere: Feltinitializarer skyder kun på den første tråd ThreadStatic-data kræver eksplicit oprydning (f.eks. i EndRequest), fordi selvom tråden er tilgængelig, vil ThreadStatic-dataene ikke blive GC'et, så du kan lække ressourcer. ThreadStatic-data er kun gode inden for en forespørgsel, fordi den næste anmodning kan komme ind i en anden tråd og hente en andens data. Scott Hanselman har ret, at ThreadStatic ikke spiller godt sammen med ASP.Net, men forklarer ikke fuldt ud hvorfor.
Lagring i CallContext afhjælper nogle af disse problemer, da konteksten dør ved slutningen af forespørgslen, og GC vil opstå til sidst (selvom du stadig kan lække ressourcer, indtil GC sker, hvis du opbevarer engangsartikler). Derudover er CallContext måden, HttpContext bliver gemt på, så det må være i orden, ikke?. Uanset hvad, skulle man tro (som jeg gjorde), at hvis man ryddede op efter sig selv efter hver anmodning, ville alt være fint: "Hvis du initialiserer ThreadStatic-variablen i starten af forespørgslen og korrekt håndterer det refererede objekt til sidst i forespørgslen, vil jeg risikere at påstå, at der ikke sker noget dårligt
"Nu,Jeg kan tage fejl. CLR kan stoppe med at hoste en tråd halvvejs, serialisere dens stack et sted, give den en ny stack og lade den begynde at køre。 Det tvivler jeg stærkt på. Jeg gætter på, at det er tænkeligt, at hyperthreading også ville gøre tingene svære, men jeg tvivler også på det. ”
Jeff Newsom
"Hvis du initialiserer en ThreadStatic-variabel i starten af en forespørgsel, og du korrekt bortskaffer det refererede objekt ved slutningen af anmodningen, vil jeg vove mig og påstå, at intet Dårligt vil ske. Du er endda cool mellem kontekster i samme AppDomain
"Nu kan jeg tage fejl i det her. CLR'en kunne potentielt stoppe en managed thread midt i streamen, serialisere dens stack et sted, give den en ny stack og lade den begynde at køre. Det tvivler jeg stærkt på. Jeg formoder, at det er tænkeligt, at hypertrådning også gør tingene svære, men det tvivler jeg også på." Jef Newsom Opdatering: Dette er misvisende. Jeg vil forklare nærmere senere, at denne trådbytte kun kan ske mellem BeginRequest og Page_Load, men Jefs reference skaber et meget stærkt billede, som jeg ikke formåede at rette med det samme.
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. Så på et tidspunkt beslutter ASP.NET, at der er for mange I/O-tråde, der behandler andre anmodninger. […] Den accepterer kun anmodninger og sætter dem i kø i et internt køobjekt inden for ASP.NET runtime. Derefter, efter køen, vil I/O-tråden anmode om en worker-tråd, og derefter vender I/O-tråden tilbage til sin pool. […] Derfor vil ASP.NET lade den medarbejder håndtere anmodningen. Den bringer den til ASP.NET køretid, ligesom I/O-tråden ville gøre ved lav belastning.
Så på et tidspunkt beslutter ASP.NET, at der er for mange I/O-tråde, der behandler andre forespørgsler. [...] Den tager bare anmodningen og sætter den i kø i dette interne køobjekt inden for ASP.NET runtime. Når den er sat i kø, vil I/O-tråden bede om en worker-tråd, og så vil I/O-tråden blive returneret til sin pool. [...] Så ASP.NET vil lade den worker-tråd behandle anmodningen. Den tager det ind i ASP.NET runtime, ligesom I/O-tråden ville have under lav belastning. Jeg har altid vidst det, men jeg tror, det skete meget tidligt, og jeg var ligeglad. Men jeg ser ud til at tage fejl. Vi havde et problem i ASP.Net app, hvor en bruger klikkede på et link efter at have klikket på et andet link, og vores app havde en null reference exception i en af singletonerne (jeg brugte CallContext i stedet for ThreadStatic til singletonen, men det viste sig at være irrelevant).
Jeg lavede lidt research på, hvordan ASP.Net tråde præcist fungerer, og fik modstridende meninger forklædt som fakta (anmodninger er tråd-agile i forespørgsler, mens forespørgsler er forankret i tråde i deres levetid), så jeg kopierede mit problem i en testapplikation med en langsom side (sov et øjeblik) og en hurtig side. Jeg klikker på linket til den langsomme side, og før siden vender tilbage, klikker jeg på linket til den hurtige side. Resultatet (log4net-dump af, hvad der sker) blæste mig bagover.
Outputtet viser, at for den anden anmodning aktiveres BeginRequest-begivenheden og sidekonstruktøren i HttpModule-pipelinen på én tråd, men page_Load i en anden. Den anden tråd har allerede migreret HttpContext fra den første tråd, men ikke CallContext eller ThreadStatic (bemærk: da HttpContexten selv er gemt i CallContext, betyder det, at ASP.Net eksplicit migrerer HttpContext). Lad os sige det igen:
Jeg har altid vidst det, men jeg antog, at det skete tidligt nok i processen til, at jeg ikke gad det. Det ser dog ud til, at jeg tog fejl. Vi har haft et problem i vores ASP.Net-app, hvor brugeren klikker på ét link lige efter at have klikket på et andet, og vores app eksploderer med en null-reference-undtagelse for en af vores singletons (jeg bruger CallContext, ikke ThreadStatic for singletonen, men det viser sig, at det ikke betyder noget).
Jeg lavede lidt research om, hvordan ASP præcis fungerer. Nets tråding virker, og jeg fik modstridende meninger, der udgiver sig for at være fakta (anmodninger er tråd-agile inden for en anmodning, mens anmodninger er fastgjort til en tråd i deres levetid), så jeg replikerede min Problem i en testapplikation med en langsom side (sover et øjeblik) og en hurtig side. Jeg klikker på linket til den langsomme side, og før siden kommer tilbage, klikker jeg på linket til den hurtige side. Resultaterne (en log4net-dump af, hvad der foregår) overraskede mig.
Outputtet viser, at – for den anden forespørgsel – aktiveres BeginRequest-hændelserne i HttpModule-pipelinen og sidekonstruktøren på én tråd, men Page_Load aktiveres på en anden. Den anden tråd har fået HttpContext migreret fra den første, men ikke CallContext eller ThreadStatic's (NB: da HttpContext selv er gemt i CallContext, betyder det, at ASP.Net er eksplicit migrerer HttpContext over). Lad os bare forklare det igen:
- Trådskift sker efter, at IHttpHandler er oprettet
- Efter at sidens feltinitialisator og konstruktør kører
- Efter alle BeginRequest-, AuthenticateRequest- og AquireSessionState-typer begivenheder, der bruges af Global.ASA/IHttpModules.
- Kun HttpContext migreres til den nye tråd
Trådskiftet sker efter, at IHttpHandler er oprettet Efter at sidens feltinitialisatorer og konstruktør kører Efter alle BeginRequest-, AuthenticateRequest- og AquireSessionState-agtige begivenheder, som dine Global.ASA / IHttpModules bruger. Kun HttpContext migrerer til den nye tråd Det er virkelig besværligt, fordi det ud fra hvad jeg har set, betyder, at den eneste persistensmulighed for adfærden af "ThreadStatic"-klassen i ASP.Net er at bruge HttpContext. Så for dine forretningsobjekter fortsætter du enten med at bruge if(HttpContext.Current!). =null) og System.Web-reference (øv), eller også skal du finde på en slags providermodel for statisk persistens, som skal sættes op, før du får adgang til disse singletons. Dobbelt kvalme.
Sig venligst, at det ikke er tilfældet.
Appendiks: Fuld log:
Det er virkelig irriterende, for så vidt jeg kan se, betyder det, at den eneste persistensmulighed for 'ThreadStatic'-lignende adfærd i ASP.Net er at bruge HttpContext. Så for dine forretningsobjekter er du enten nødt til if(HttpContext.Current!=null) og System.Web-referencen (øv), eller også må du finde på en slags udbydermodel til din statisk persistens, som skal oprettes, før nogen af disse singletons tilgås. Dobbelt ulækkert.
Sig venligst, at det ikke er sandt.
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() - Langsom sidesøvn....
[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(now)=1835, calldata=2720, logicalcalldata=2720, threadstatic=2720
[3748] INFO 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Langsom side vågner.... [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(now)=97, calldata=3748 [3748] INFO 11:10:06,350 ASP. Global_asax. Application_EndRequest() - SLUT /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(now)=1703, calldata= [4748] INFO 11:10:06,791 ASP. Global_asax. Application_EndRequest() - SLUT /ConcurrentRequestsDemo/FastPage.aspx Nøglen er, hvad der sker, når FastPages Page_Load udløses. ThreadID er 4748, men ThreadID'en, jeg gemte i ctors HttpContext, er 2720. Hashkoden for logiske tråde er 1703, men hashkoden jeg gemmer i ctor er 1835. Alle de data, jeg har gemt i CallContext, mangler (selv dataene markeret ILogicalThreadAffinnative), men HttpContext er stadig der. Som du måske forventer, er min ThreadStatic også væk.
Det afgørende er, hvad der sker, når FastPages Page_Load affyres. ThreadID'en er 4748, men ThreadID'en, jeg gemte i HttpContext i ctor'en, er 2720. Hashkoden for den logiske tråd er 1703, men den, jeg gemte i ctor'en, er 1835. Alle data, jeg har gemt i CallContext, er væk (selv den markerede ILogicalThreadAffinative), men HttpContext er stadig der. Som forventet er min ThreadStatic også væk. (Slut)
|