Tento článek je zrcadlovým článkem o strojovém překladu, klikněte zde pro přechod na původní článek.

Pohled: 3568|Odpověď: 2

[Zdroj] ASP.NET rozdíl mezi ThreadStatic, CallContext a HttpContext

[Kopírovat odkaz]
Zveřejněno 30.06.2023 20:34:10 | | |
Původní:Přihlášení k hypertextovému odkazu je viditelné.



Shrnutí:
I když si myslíte, že víte, co děláte, není bezpečné ukládat cokoli do úložiště ThreadStatic member, CallContext nebo Thread Local v ASP.Net aplikaci, pokud byla hodnota nastavena předem. do Page_Load (např. v IHttpModule nebo page constructoru), ale během nebo po přístupu.

[Aktualizace: srpen 2008 Vzhledem k tomu, že na tento článek stále odkazuje poměrně dost lidí, považuji za nutné upřesnit, že chování při výměně vláken nastává v velmi specifickém bodě životního cyklu stránky, a ne tehdy, kdy se to tak cítí.] Moje formulace po citaci Jeffa Newsoma je nešťastná. Jinak mě velmi těší (a těší mě), že se na tento článek odkazuje několikrát v diskuzích o správném zacházení s HttpContextem. Jsem rád, že to lidé považují za užitečné. ]

Existuje mnoho zmatků ohledně toho, jak implementovat uživatelsky specifické singletony v ASP.Net – tedy globální data jsou globální pouze pro jednoho uživatele nebo požadavek. To není neobvyklý požadavek: publikovat transakční, bezpečnostní kontextová nebo jiná "globální" data na jednom místě, místo aby byla vkládána do každého volání metody, protože loam data umožňují jasnější (a čitelnější) implementaci. Pokud ale nejste opatrní, je to skvělé místo, kde si můžete ublížit do nohy (nebo hlavy). Myslel jsem, že vím, co se děje, ale nevěděl jsem to.

Preferovanou možností bude singletonUloženo v HttpContext.Current.Items, jednoduché a bezpečné, ale vztahuje daný singleton k jeho použití v aplikaci ASP.Net. Pokud singleton selže ve vašem obchodním objektu, není to ideální. I když přístup k vlastnostem zabalíte do příkazu if


Summary:
I když si myslíte, že víte, co děláte, není bezpečné ukládat cokoli do člena ThreadStaticu, CallContext nebo Thread Local Storage v ASP.Net aplikaci, pokud je to možné že hodnota může být nastavena před Page_Load (např. v IHttpModule nebo page constructoru), ale přistupuje se k ní během nebo po ní.

[Aktualizace: srpen 2008 Vzhledem k poměrně velkému počtu lidí, kteří stále odkazují na tento příspěvek, cítím potřebu upřesnit, že toto chování při výměně vláken se odehrává na velmi konkrétním místě na stránce Životní cyklus a ne kdykoliv-se-chce. Moje formulace po citátu Jefa Newsona byla nešťastná. Kromě toho jsem byl nesmírně potěšen (a polichocen) z toho, kolikrát jsem viděl tento příspěvek citovaný v diskuzích o správném zacházení s HttpContext. Jsem rád, že to lidem pomohlo.]

Existuje mnoho zmatků ohledně toho, jak implementovat uživatelsky specifické singletony v ASP.Net – tedy globální data, která jsou globální pouze pro jednoho uživatele nebo požadavek. Není to neobvyklý požadavek: publikovat transakce, bezpečnostní kontext nebo jiná "globální" data na jednom místě, místo aby se tyto údaje protlačovaly přes každé volání metody, jak to může být tramp data čistší (a čitelnější) implementace. Nicméně je to skvělé místo, kde si můžete střelit do nohy (nebo hlavy), pokud nejste opatrní. Myslel jsem, že vím, co se děje, ale nevěděl jsem to.

Preferovanou možností, tedy uložením singletonů do HttpContext.Current.Items, je jednoduchá a bezpečná, ale váží daný singleton na použití v ASP.Net aplikaci. Pokud je singleton dole ve vašich obchodních objektech, není to ideální. I když zabalíte přístup k vlastnostem do příkazu if

... pak stále musíte odkazovat na System.Web z té sestavy, což obvykle zařazuje více "webby" objektů na špatná místa.

Alternativou je použít statický člen [ThreadStatic], lokální úložiště Thread (což je v podstatě totéž) nebo CallContext.

Problémy s [ThreadStatic] jsou dobře zdokumentované, ale shrnutí:
Field initalizery se spustí až při prvním vlákně
Data ThreadStatic potřebují explicitní vyčištění (například v EndRequest), protože i když je Thread dostupný, data ThreadStatic nebudou chráněna jako GC, takže můžete unikat zdroje.
ThreadStatic data jsou dobrá jen v rámci požadavku, protože další požadavek může přijít v jiném vlákně a získat data někoho jiného.
Scott Hanselman to vystihl správně, že ThreadStatic si s ASP.Net nehraje dobře, ale úplně nevysvětluje proč.

Úložiště v CallContext některé z těchto problémů zmírňuje, protože kontext na konci požadavku vymizí a Zelená karta nakonec nastane (i když stále můžete unikat zdroje, dokud nenastane GC, pokud skladujete jednorázové předměty). Navíc CallContext je způsob, jak se HttpContext ukládá, takže to musí být v pořádku, ne? Bez ohledu na to byste si (jako já) mysleli, že pokud po sobě na konci každé žádosti uklidíte, bude vše v pořádku:
"Pokud inicializujete proměnnou ThreadStatic na začátku požadavku a správně zpracujete odkazovaný objekt na konci požadavku, riskoval bych tvrzení, že se nic špatného nestane

"Teď,Mohu se mýlit. CLR může přestat hostovat vlákno v polovině, serializovat jeho zásobník někde, dát mu nový zásobník a nechat ho začít spouštět。 O tom silně pochybuji. Myslím, že hyperthreading by také mohl věci ztížit, ale také o tom pochybuji. ”

Jeff Newsom


"Pokud inicializujete proměnnou ThreadStatic na začátku požadavku a správně odstraníte odkazovaný objekt na konci požadavku, troufnu si a tvrdím, že nic Stane se špatné. Jsi v pohodě i mezi kontexty ve stejné AppDomain

"Teď se možná pletu. CLR by mohl potenciálně zastavit spravované vlákno uprostřed proudu, serializovat jeho zásobník někde ven, dát mu nový zásobník a nechat ho začít spouštět. Silně o tom pochybuji. Předpokládám, že je možné, že hypervlákno to také ztěžuje, ale také o tom pochybuji."
Jef Newsom
Aktualizace: To je zavádějící. Později vysvětlím, že tato výměna vláken může probíhat jen mezi BeginRequest a Page_Load, ale Jefův odkaz vytváří velmi silný obraz, který jsem hned neopravil.

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.
Takže ASP.NET určitě rozhodnout, že je příliš mnoho I/O vláken zpracovávajících jiné požadavky. […] Přijímá pouze požadavky a zařazuje je do interního objektu fronty během ASP.NET běhu. Poté, po zařazení fronty, I/O vlákno požádá o pracovní vlákno a I/O vlákno se vrátí do svého poolu. […] Proto ASP.NET nechá tuto žádost vyřídit na tomto pracovníkovi. To ho přivede do ASP.NET runtime, stejně jako I/O vlákno při nízkém zatížení.

Takže v určitém bodě ASP.NET rozhodne, že je příliš mnoho I/O vláken zpracovávajících jiné požadavky. [...] Jednoduše vezme požadavek a zařadí ho do tohoto interního objektu fronty v rámci ASP.NET runtime. Poté, co je to ve frontě, I/O vlákno požádá o pracovní vlákno a I/O vlákno se vrátí do svého poolu. [...] Takže ASP.NET nechá toto pracovní vlákno zpracovat požadavek. Přijme to do ASP.NET runtime, stejně jako by to udělalo I/O vlákno při nízkém zatížení.

Vždycky jsem o tom věděl, ale myslím, že se to stalo velmi brzy a bylo mi to jedno. Ale zdá se, že se mýlím. Měli jsme problém v ASP.Net aplikaci, kdy uživatel klikl na odkaz po kliknutí na jiný odkaz a naše aplikace měla výjimku nulové reference v jednom z singletonů (použil jsem CallContext místo ThreadStatic pro singleton, ale ukázalo se, že to bylo irelevantní).

Dělal jsem si průzkum o tom, jak přesně ASP.Net vlákna fungují, a dostal jsem protichůdné názory maskované jako fakta (požadavky jsou v požadavcích agilní, zatímco požadavky jsou ukotvené na vláknech během své existence), takže jsem svůj problém zkopíroval v testovací aplikaci s pomalou stránkou (na chvíli usnul) a rychlou stránkou. Kliknu na odkaz na pomalou stránku a než se stránka vrátí, kliknu na odkaz na rychlou stránku. Výsledek (log4net dump toho, co se děje) mě úplně ohromil.

Výstup ukazuje, že u druhého požadavku se událost BeginRequest a konstruktor stránky v pipeline HttpModule spustí na jednom vlákně, ale page_Load na jiném. Druhé vlákno již migrovalo HttpContext z prvního vlákna, ale ne CallContext ani ThreadStatic (poznámka: protože samotný HttpContext je uložen v CallContext, znamená to, že ASP.Net explicitně migruje HttpContext). Řekněme to znovu:


Vždycky jsem o tom věděl, ale předpokládal jsem, že se to stalo dost brzy na to, aby mi to bylo jedno. Zdá se však, že jsem se mýlil. Máme problém v naší ASP.Net aplikaci, kdy uživatel klikne na jeden odkaz hned po dalším a naše aplikace se roztrhne s nulovou referencí pro jeden z našich singletonů (používám CallContext, ne ThreadStatic pro singleton, ale ukazuje se, že na tom nezáleží).

Trochu jsem si o tom zjišťoval, jak přesně ASP funguje. Netovo vlákno funguje a narazilo na protichůdné názory – které se tváří jako fakta (požadavky jsou v rámci požadavku agilní vs. požadavky jsou připnuté k vláknu po celou dobu života), takže jsem replikoval svůj Problém v testovací aplikaci s pomalou stránkou (na chvíli usne) a rychlou stránkou. Kliknu na odkaz na pomalou stránku a než se stránka vrátí, kliknu na odkaz na rychlou stránku. Výsledky (log4net výpis toho, co se děje) mě překvapily.

Výstup ukazuje, že u druhého požadavku se události BeginRequest v pipeline HttpModule a page constructor spustí na jednom vlákně, ale Page_Load na jiném. Druhé vlákno mělo HttpContext migrovaný z prvního, ale ne CallContext ani ThreadStatic (Poznámka: protože HttpContext je sám uložen ve CallContext, znamená to, že ASP.Net je explicitně migrující HttpContext napříč). Pojďme si to znovu vysvětlit:

  • Přepínání vláken probíhá po vytvoření IHttpHandleru
  • Po spuštění inicializátoru pole a konstruktoru stránky
  • Po jakýchkoli událostech typu BeginRequest, AuthenticateRequest, AquireSessionState, které používají Global.ASA/IHttpModules.
  • Pouze HttpContext je migrován do nového vlákna



Přepnutí vlákna nastává po vytvoření IHttpHandleru
Po spuštění pole a konstruktoru stránky
Po jakýchkoli událostech typu BeginRequest, AuthenticateRequest, AquireSessionState, které používají vaše Global.ASA / IHttpModule.
Pouze HttpContext migruje do nového vlákna

To je velká otrava, protože podle toho, co jsem viděl, to znamená, že jedinou možností perzistence chování třídy "ThreadStatic" v ASP.Net je použít HttpContext. Takže pro vaše obchodní objekty buď pokračujte v používání if(HttpContext.Current!). =null) a referencí na System.Web (fuj), nebo musíte vytvořit nějaký model poskytovatele pro statickou perzistenci, který je potřeba nastavit před přístupem k těmto singletonům. Dvojitá nevolnost.

Prosím, řekněte mi, že tomu tak není.

Příloha: Kompletní záznam:


To je velká otrava, protože podle mě to znamená, že jedinou možností perzistence pro chování podobné 'ThreadStatic', ASP.Net je použití HttpContext. Takže u vašich obchodních objektů buď zůstanete u if(HttpContext.Current!=null) a referencí System.Web (fuj), nebo musíte vymyslet nějaký model poskytovatele Statická perzistence, kterou bude třeba nastavit před tím, než se k těmto singletonům přistupuje. Dvojité fuj.

Prosím, ať to tak není.

Appendix: That log in full:
[3748] INFORMACE 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/SlowPage.aspx
[3748] INFORMACE 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(ny)=97, calldata=
[3748] INFORMACE 11:10:05,249 ASP. SlowPage_aspx.. ctor() - threadid=3748, threadhash=(cctor)97, threadhash(nyní)=97, calldata=3748, logicalcalldata=3748
[3748] INFORMACE 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(nyní)=97, calldata=3748, logicalcalldata=3748
[3748] INFORMACE 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - Pomalé spánkové spánky....

[2720] INFORMACE 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/FastPage.aspx
[2720] INFORMACE 11:10:05,679 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(ny)=1835, calldata=
[2720] INFORMACE 11:10:05,679 ASP. FastPage_aspx.. ctor() - threadid=2720, threadhash=(cctor)1835, threadhash(ny)=1835, calldata=2720, logicalcalldata=2720, threadstatic=2720

[3748] INFORMACE 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Pomalu se probouzí stránka....
[3748] INFORMACE 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(nyní)=97, calldata=3748, logicalcalldata=3748
[3748] INFORMACE 11:10:06,350 ASP. Global_asax. Application_EndRequest() - threadid=3748, threadhash=97, threadhash(ny)=97, calldata=3748
[3748] INFORMACE 11:10:06,350 ASP. Global_asax. Application_EndRequest() - KONEC /ConcurrentRequestsDemo/SlowPage.aspx

[4748] INFORMACE 11:10:06,791 ASP. FastPage_aspx. Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(ny)=1703, calldata=, logicalcalldata=, threadstatic=
[4748] INFORMACE 11:10:06,791 ASP. Global_asax. Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(ny)=1703, calldata=
[4748] INFORMACE 11:10:06,791 ASP. Global_asax. Application_EndRequest() - KONEC /ConcurrentRequestsDemo/FastPage.aspx
Klíčové je, co se stane, když se spustí Page_Load FastPage. ThreadID je 4748, ale ThreadID, které jsem uložil do HttpContext ctoru, je 2720. Hashovací kód pro logická vlákna je 1703, ale hash kód, který ukládám do ctor (CTOR), je 1835. Všechna data, která jsem uložil do CallContextu, chybí (dokonce i data označená ILogicalThreadAffinnative), ale HttpContext tam stále je. Jak asi čekáte, můj ThreadStatic je také pryč.

Klíčové je, co se stane, když se spustí Page_Load FastPage. ThreadID je 4748, ale threadID, které jsem uložil do HttpContext v ctoru, je 2720. Hash kód logického vlákna je 1703, ale ten, který jsem uložil do ctor (CTOR), je 1835. Všechna data, která jsem uložil do CallContext, jsou pryč (i ta označená jako ILogicalThreadAffinative), ale HttpContext tam stále je. Jak byste čekali, i můj ThreadStatic je pryč.
(Konec)




Předchozí:.NET/C# kolekce Any() nebo Count(), což je rychlejší
Další:Jak Lazy v C# chrání vlákna
 Pronajímatel| Zveřejněno 30.06.2023 20:35:23 |
 Pronajímatel| Zveřejněno 02.07.2023 9:59:06 |
CallContext

Jmenný prostor: System.Runtime.Redáling.Messaging
Type-fully qualified název: System.Runtime.Remoting.Messaging.CallContext

Účel: Poskytnout sadu atributů, které jsou předávány spolu s cestou k vykonávání kódu, jednoduše řečeno: umožnit předávání dat v cestě pro vykonání vláknového (vícevláknového/jednovláknového) kódu.
metodapopisZda lze použít v vícevláknovém prostředí
SetDataUložit daný objekt a přiřadit mu specifikované jméno.ne
GetDataZískejte objekt s určeným názvem ze System.Runtime.Remoting.Messaging.CallContextne
LogicalSetDataUložit daný objekt do kontextu logického volání a přiřadit mu specifikované jméno.být
LogicalGetDataZískejte objekty s určenými názvy z kontextu logického volání.být
FreeNamedDataSlotVyprázdněte datové sloty s uvedeným názvem.být
HostKontextZískejte nebo nastavte hostitelský kontext spojený s aktuálním vláknem. V webovém prostředí je rovna System.Web.HttpContext.Currentne


Zřeknutí se:
Veškerý software, programovací materiály nebo články publikované organizací Code Farmer Network slouží pouze k učení a výzkumu; Výše uvedený obsah nesmí být používán pro komerční ani nelegální účely, jinak nesou všechny důsledky uživatelé. Informace na tomto webu pocházejí z internetu a spory o autorská práva s tímto webem nesouvisí. Musíte výše uvedený obsah ze svého počítače zcela smazat do 24 hodin od stažení. Pokud se vám program líbí, podporujte prosím originální software, kupte si registraci a získejte lepší skutečné služby. Pokud dojde k jakémukoli porušení, kontaktujte nás prosím e-mailem.

Mail To:help@itsvse.com