Dit artikel is een spiegelartikel van machinevertaling, klik hier om naar het oorspronkelijke artikel te gaan.

Bekijken: 3568|Antwoord: 2

[Bron] ASP.NET het verschil tussen ThreadStatic, CallContext en HttpContext

[Link kopiëren]
Geplaatst op 30-06-2023 20:34:10 | | |
Origineel:De hyperlink-login is zichtbaar.



Samenvatting:
Zelfs als je denkt dat je weet wat je doet, is het niet veilig om iets op te slaan in een ThreadStatic-lid, CallContext of Thread Local-opslag in een ASP.Net applicatie als de waarde mogelijk van tevoren is ingesteld. om te Page_Load (bijvoorbeeld in IHttpModule of page constructor), maar tijdens of na de toegang.

[Update: augustus 2008 Aangezien nogal wat mensen dit artikel blijven linken, vind ik het noodzakelijk te verduidelijken dat dit thread-swapgedrag plaatsvindt op een heel specifiek moment in de levenscyclus van de pagina, en niet wanneer het zo voelt.] Mijn formulering na het citeren van Jeff Newsom is jammer. Verder ben ik erg blij (en gevleid) om meerdere keren verwijzingen naar dit artikel te zien in ontwerpdiscussies over de juiste omgang met HttpContext. Ik ben blij dat mensen het nuttig vinden. ]

Er is veel verwarring over hoe gebruikersspecifieke singletons in ASP.Net geïmplementeerd moeten worden – dat wil zeggen, globale data is globaal voor slechts één gebruiker of verzoek. Dit is geen ongebruikelijke vereiste: publiceer transactionele, beveiligingscontext of andere "globale" data op één plek, in plaats van deze in elke methode-aanroep te plaatsen, omdat loam-data een duidelijkere (en leesbaardere) implementatie mogelijk maakt. Maar als je niet oppast, is dit een geweldige plek om jezelf in de voet (of het hoofd) te schieten. Ik dacht dat ik wist wat er aan de hand was, maar dat deed ik niet.

De voorkeursoptie is singletonOpgeslagen in HttpContext.Current.Items, eenvoudig en veilig, maar relateert de betreffende singleton aan het gebruik ervan in de ASP.Net toepassing. Als de singleton faalt in je bedrijfsobject, is dit niet ideaal. Zelfs als je property-toegang in een if-statement verpakt


Summary:
Zelfs als je denkt dat je weet wat je doet, is het niet veilig om iets op te slaan in een ThreadStatic-lid, CallContext of Thread Local Storage binnen een ASP.Net applicatie, als dat mogelijk is dat de waarde vooraf ingesteld kan worden ingesteld Page_Load (bijvoorbeeld in IHttpModule of page constructor), maar tijdens of na het gebruik wordt benaderd.

[Update: aug 2008 Gezien het vrij grote aantal mensen dat deze post blijft linken, voel ik de behoefte te verduidelijken dat dit thread-swapping gedrag op een heel specifiek punt op de pagina plaatsvindt levenscyclus en niet wanneer het maar wilt. Mijn formulering na het citaat van Jef Newson was jammer. Los daarvan ben ik enorm tevreden (en gevleid) door het aantal keren dat ik deze post heb zien worden geciteerd binnen designdiscussies over het juiste omgaan met HttpContext. Ik ben blij dat mensen het nuttig vonden.]

Er is veel verwarring over hoe je gebruikersspecifieke singletons in ASP.Net implementeert – dat wil zeggen globale data die alleen globaal is voor één gebruiker of verzoek. Dit is geen ongebruikelijke vereiste: het publiceren van transacties, beveiligingscontext of andere 'globale' data op één plek, in plaats van het door elke methode-aanroep te sturen zoals tramp-data kan zorgen voor een Schonere (en beter leesbare) implementatie. Het is echter een geweldige plek om jezelf in de voet (of het hoofd) te schieten als je niet oppast. Ik dacht dat ik wist wat er aan de hand was, maar dat deed ik niet.

De voorkeursoptie, het opslaan van je singletons in HttpContext.Current.Items, is eenvoudig en veilig, maar koppelt de betreffende singleton aan het gebruik binnen een ASP.Net applicatie. Als de singleton in je zakelijke objecten staat, is dit niet ideaal. Zelfs als je de property-access in een if-statement verpakt

... dan moet je nog steeds System.Web verwijzen vanuit die assembly, wat vaak meer 'webby'-objecten op de verkeerde plek encoreert.

De alternatieven zijn het gebruik van een [ThreadStatic] statisch lid, Thread local storage (wat eigenlijk hetzelfde is), of CallContext.

De problemen met [ThreadStatic] zijn goed gedocumenteerd, maar om samen te vatten:
Veldinitalizers vuren alleen op de eerste draad
ThreadStatic-data moet expliciet worden opgeschonden (bijvoorbeeld in EndRequest), want hoewel de ThreadStatic-data bereikbaar is, wordt de ThreadStatic-data niet GC'd, dus je kunt mogelijk bronnen lekken.
ThreadStatic-data is alleen goed binnen een verzoek, omdat het volgende verzoek in een andere thread kan binnenkomen en de data van iemand anders kan krijgen.
Scott Hanselman krijgt het goed, namelijk dat ThreadStatic niet goed met ASP.Net werkt, maar legt niet volledig uit waarom.

Opslag in CallContext verlicht enkele van deze problemen, omdat de context aan het einde van het verzoek sterft en GC uiteindelijk zal optreden (hoewel je nog steeds resources kunt lekken totdat de GC plaatsvindt als je bewaart wegwerpspullen). Bovendien wordt CallContext opgeslagen met CallContext, dus het moet toch oké zijn? Hoe dan ook, je zou denken (zoals ik) dat, mits je aan het einde van elk verzoek achter jezelf opruimt, alles goed zou zijn:
"Als je de ThreadStatic-variabele initialiseert aan het begin van het verzoek en het referentieobject correct afhandelt aan het einde van het verzoek, loop ik het risico te beweren dat er niets slechts zal gebeuren

"Nu,Ik kan het mis hebben. CLR kan halverwege stoppen met het hosten van een thread, ergens serialiseren, een nieuwe stack geven en laten beginnen met uitvoeren。 Ik betwijfel dit ten zeerste. Ik denk dat het denkbaar is dat hyperthreading het ook moeilijk maakt, maar ik betwijfel het ook. ”

Jeff Newsom


"Als je een ThreadStatic-variabele initialiseert aan het begin van een verzoek, en je verwijdert het referentieobject correct aan het einde van het verzoek, ga ik het risico nemen en beweren dat er niets gebeurt Er zal iets slechts gebeuren. Je bent zelfs cool tussen contexten in hetzelfde AppDomain

"Nu, ik kan het mis hebben. De clr kan mogelijk een managed thread halverwege stoppen, ergens de stack serialiseren, hem een nieuwe stack geven en hem laten uitvoeren. Ik betwijfel het ten zeerste. Ik veronderstel dat het denkbaar is dat hyperthreading het ook moeilijk maakt, maar ik betwijfel dat ook."
Jef Newsom
Update: Dit is misleidend. Ik zal later verder uitleggen dat deze thread-swap alleen kan plaatsvinden tussen BeginRequest en Page_Load, maar Jefs referentie creëert een zeer krachtig beeld dat ik niet meteen heb kunnen corrigeren.

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.
Dus op een gegeven moment besluiten ASP.NET dat er te veel I/O-threads zijn die andere verzoeken verwerken. […] Het accepteert alleen verzoeken en zet deze in een interne wachtrij-object binnen ASP.NET runtime. Na het wachtrijen zal de I/O-thread vervolgens een worker-thread aanvragen, waarna de I/O-thread terugkeert naar zijn pool. […] Daarom laat ASP.NET die werknemer het verzoek afhandelen. Het brengt het naar ASP.NET runtime, net zoals de I/O-thread bij lage belasting zou doen.

Op een gegeven moment besluit ASP.NET dat er te veel I/O-threads zijn die andere verzoeken verwerken. [...] Het neemt gewoon het verzoek en zet het in de wachtrij in dit interne wachtrijobject binnen de ASP.NET runtime. Daarna, nadat die in de wachtrij is gezet, vraagt de I/O-thread om een worker-thread, waarna de I/O-thread wordt teruggestuurd naar zijn pool. [...] Dus ASP.NET laat die worker-thread het verzoek verwerken. Het neemt het mee naar de ASP.NET runtime, net zoals de I/O-thread dat zou doen bij lage belasting.

Ik wist het altijd al, maar ik denk dat het heel vroeg gebeurde en het kon me niets schelen. Maar ik lijk het mis te hebben. We hadden een probleem in ASP.Net app waarbij een gebruiker op een link klikte nadat hij op een andere link had geklikt, en onze app had een null reference exception in een van de singletons (ik gebruikte CallContext in plaats van ThreadStatic voor de singleton, maar dat bleek irrelevant te zijn).

Ik heb wat onderzoek gedaan naar hoe ASP.Net threads precies werken, en kreeg tegenstrijdige meningen die als feiten zijn vermomd (verzoeken zijn thread-agile in requests, terwijl requests tijdens hun levensduur in threads worden verankerd), dus kopieerde ik mijn probleem in een testapplicatie met een trage pagina (even in slaap) en een snelle pagina. Ik klik op de link naar de langzame pagina, en voordat de pagina terugkeert, klik ik op de link naar de snelle pagina. Het resultaat (log4net-dump van wat er gebeurt) blies mijn verstand omver.

De output toont dat voor het tweede verzoek het BeginRequest-event en de page constructor in de HttpModule-pijplijn op de ene thread worden afgevuurd, maar in een andere thread page_Load. De tweede thread heeft de HttpContext al gemigreerd vanuit de eerste thread, maar niet de CallContext of de ThreadStatic (let op: aangezien de HttpContext zelf in de CallContext wordt opgeslagen, betekent dit dat ASP.Net expliciet de HttpContext migreert). Laten we het nog eens zeggen:


Ik wist hier altijd al van, maar ik ging ervan uit dat het vroeg genoeg in het proces gebeurde zodat het me niet kon schelen. Het lijkt er echter op dat ik het mis had. We hebben een probleem in onze ASP.Net-app waarbij de gebruiker op één link klikt vlak nadat hij op een andere klikt, en onze app explodeert met een null reference exception voor een van onze singletons (ik gebruik CallContext, niet ThreadStatic voor de singleton, maar het blijkt dat het niet uitmaakt).

Ik heb wat onderzoek gedaan naar precies hoe ASP werkt. De threading van het net werkt, en kreeg tegenstrijdige meningen die zich als feit voordoen (verzoeken zijn thread-agile binnen een verzoek versus verzoeken worden voor hun levensduur aan een thread vastgepind), dus ik heb mijn Probleem in een testapplicatie met een trage pagina (slaapt een seconde) en een snelle pagina. Ik klik op de link voor de langzame pagina en voordat de pagina terugkomt, klik ik op de link voor de snelle pagina. De resultaten (een log4net-dump van wat er gebeurt) verrasten me.

Wat de output laat zien, is dat - voor het tweede verzoek - de BeginRequest-gebeurtenissen in de HttpModule-pipeline en de page constructor op één thread worden geactiveerd, maar de Page_Load op een andere afvuurt. De tweede thread heeft de HttpContext gemigrerd van de eerste, maar niet van CallContext of ThreadStatic (NB: aangezien HttpContext zelf in CallContext wordt opgeslagen, betekent dit ASP.Net is expliciet de HttpContext overzetten). Laten we het nog eens uitleggen:

  • Thread-switching vindt plaats nadat de IHttpHandler is aangemaakt
  • Nadat de veldinitializer en constructor van de pagina worden uitgevoerd
  • Na alle BeginRequest-, AuthenticateRequest- en AquireSessionState-achtige gebeurtenissen die worden gebruikt door Global.ASA/IHttpModules.
  • Alleen de HttpContext wordt gemigrerd naar de nieuwe thread



De thread-switch vindt plaats nadat de IHttpHandler is aangemaakt
Nadat de veldinitialisatoren en constructor van de pagina worden uitgevoerd
Na alle BeginRequest-, AuthenticateRequest- en AquireSessionState-achtige gebeurtenissen die je Global.ASA / IHttpModules gebruiken.
Alleen de HttpContext migreert naar de nieuwe thread

Dit is een enorme ergernis, want uit wat ik heb gezien betekent het dat de enige persistentieoptie voor het gedrag van de "ThreadStatic"-klasse in ASP.Net het gebruik van HttpContext is. Dus voor je bedrijfsobjecten blijf je if(HttpContext.Current!) gebruiken. =null) en System.Web-referentie (bah), of je moet een soort providermodel bedenken voor statische persistentie, dat moet worden ingesteld voordat je toegang krijgt tot deze singletons. Dubbele misselijkheid.

Zeg alsjeblieft dat dit niet het geval is.

Appendix: Volledig logboek:


Dit is een enorme ergernis, want voor zover ik kan zien betekent het dat de enige persistentieoptie voor 'ThreadStatic'-achtig gedrag in ASP.Net is om HttpContext te gebruiken. Dus voor je business objects zit je ofwel vast aan de if(HttpContext.Current!=null) en de System.Web-referentie (bah), of je moet een soort providermodel voor je provider bedenken statische persistentie, die moet worden ingesteld voordat een van deze singletons wordt benaderd. Dubbel bah.

Alsjeblieft, iemand zegt dat het niet zo is.

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() - Langzaam pagina slapen....

[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() - Langzame pagina wordt wakker....
[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() - EINDE /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() - EINDE /ConcurrentRequestsDemo/FastPage.aspx
De sleutel is wat er gebeurt wanneer de Page_Load van FastPage wordt geactiveerd. De ThreadID is 4748, maar de ThreadID die ik heb opgeslagen in de HttpContext van ctor is 2720. De hashcode voor logische threads is 1703, maar de hashcode die ik in ctor opsla is 1835. Alle data die ik in de CallContext heb opgeslagen ontbreekt (zelfs de data gemarkeerd als ILogicalThreadAffinnative), maar de HttpContext is er nog steeds. Zoals je misschien verwacht, is mijn ThreadStatic ook weg.

Het belangrijkste is wat er gebeurt als de Page_Load van FastPage afgaat. De ThreadID is 4748, maar de threadID die ik in HttpContext in de ctor heb opgeslagen is 2720. De hashcode voor de logische thread is 1703, maar de hashcode die ik in de ctor heb opgeslagen is 1835. Alle data die ik in de CallContext heb opgeslagen is verdwenen (zelfs die gemarkeerde ILogicalThreadAffinative), maar HttpContext is er nog steeds. Zoals je zou verwachten, is mijn ThreadStatic ook weg.
(Einde)




Vorig:.NET/C# collectie Any() of Count() die sneller is
Volgend:Hoe Lazy in C# threads veilig houdt
 Huisbaas| Geplaatst op 30-06-2023 20:35:23 |
 Huisbaas| Geplaatst op 02-07-2023 09:59:06 |
CallContext

Naamruimte: System.Runtime.Remoting.Messaging
Type-volledig gekwalificeerde naam: System.Runtime.Remoting.Messaging.CallContext
Officiële introductie:De hyperlink-login is zichtbaar.

Doel: Het bieden van een set attributen die samen met het uitvoeringscodepad worden doorgegeven, simpel gezegd: het bieden van de mogelijkheid om gegevens door te geven in het uitvoeringspad van threaded (multi-threaded/single-threaded) code.
methodebeschrijvingOf het gebruikt kan worden in een multithreaded omgeving
SetDataSla een bepaald object op en koppel het aan een gespecificeerde naam.niet
GetDataHaal het object met de opgegeven naam op van System.Runtime.Remoting.Messaging.CallContextniet
LogicalSetDataSla een bepaald object op in de context van een logische aanroep en koppel het aan een gespecificeerde naam.zijn
LogicalGetDataHaal objecten op met gespecificeerde namen uit de context van de logische aanroep.zijn
FreeNamedDataSlotMaak de dataslots leeg met de opgegeven naam.zijn
HostContextKrijg of stel de hostcontext die aan de huidige thread is gekoppeld. In de webomgeving is het gelijk aan System.Web.HttpContext.Currentniet


Disclaimer:
Alle software, programmeermaterialen of artikelen die door Code Farmer Network worden gepubliceerd, zijn uitsluitend bedoeld voor leer- en onderzoeksdoeleinden; De bovenstaande inhoud mag niet worden gebruikt voor commerciële of illegale doeleinden, anders dragen gebruikers alle gevolgen. De informatie op deze site komt van het internet, en auteursrechtconflicten hebben niets met deze site te maken. Je moet bovenstaande inhoud volledig van je computer verwijderen binnen 24 uur na het downloaden. Als je het programma leuk vindt, steun dan de echte software, koop registratie en krijg betere echte diensten. Als er sprake is van een inbreuk, neem dan contact met ons op via e-mail.

Mail To:help@itsvse.com