Eredeti:A hiperlink bejelentkezés látható.
Összefoglalás: Még ha azt hiszed, tudod, mit csinálsz, nem biztonságos bármit is tárolni egy ThreadStatic tagban, CallContextben vagy Thread Local store-ban egy ASP.Net alkalmazásban, ha az értéket előre be volt állítva. Page_Load (pl. IHttpModulban vagy oldalkonstruktorban), de hozzáférés közben vagy után.
[Frissítés: 2008. augusztus Mivel sokan továbbra is linkelnek erre a cikkre, szükségesnek érzem tisztázni, hogy ez a szálcsere viselkedése az oldal életciklusának egy nagyon konkrét pontján történik, nem pedig akkor, amikor úgy érzem.] A Jeff Newsom idézése után a megfogalmazásom sajnálatos. Ettől eltekintve nagyon örülök (és megtisztelődnek), hogy többször is hivatkoznak erre a cikkre a HttpContext megfelelő kezeléséről szóló tervezési vitákban. Örülök, hogy az emberek hasznosnak találják. ]
Sok a félreértés abban, hogyan lehet felhasználó-specifikus singletonokat implementálni ASP.Net-ben – vagyis a globális adatok csak egy felhasználó vagy kérés esetén globálisak. Ez nem ritka követelmény: egy helyen tüntess közzé tranzakciós, biztonsági kontextust vagy más "globális" adatokat, ahelyett, hogy minden módszerhívásba betolnád őket, mert a loam adatok világosabb (és olvashatóbbak) megvalósítást biztosítanak. Ha viszont nem vagy óvatos, ez egy remek hely, ahol magad a lábba (vagy fejbe) lőheted. Azt hittem, tudom, mi történik, de nem tudtam.
A preferált opció a singleton leszA HttpContext.Current.Items adatbázisban tárolva, egyszerű és biztonságos, de az érintett singletont a ASP.Net alkalmazásában való használatához kapcsolja. Ha az egyszemélyes eszköz megbukik az üzleti célodban, akkor ez nem ideális. Még akkor is, ha az ingatlanhozzáférést if utasításba csomagolod
Summary: Még ha azt hiszed, tudod, mit csinálsz, nem biztonságos bármit is tárolni egy ThreadStatic tagban, CallContextben vagy Thread Local Storage-ban egy ASP.Net alkalmazásban, ha van rá lehetőség hogy az érték Page_Load előtt be van állítva (például az IHttpModule-ban, vagy oldalkonstruktorban), de elérhető közben vagy utána.
[Frissítés: 2008. augusztus Tekintettel arra, hogy viszonylag sokan linkelnek erre a bejegyzésre, tisztáznom kell, hogy ez a thread-váltási viselkedés nagyon konkrét ponton történik az oldalon Életciklus, és nem amikor csak úgy érzed. A Jef Newson-idézet után a megfogalmazásom sajnálatos volt. Ettől függetlenül rendkívül elégedett (és megtisztelődő) számomra, hogy hányszor láttam ezt a bejegyzést idézni a HttpContext megfelelő kezeléséről szóló tervezési vitákban. Örülök, hogy az emberek hasznosnak találták.]
Sok a zavar azzal kapcsolatban, hogyan lehet felhasználó-specifikus singletonokat implementálni ASP.Net-ben – vagyis globális adatok, amelyek csak egy felhasználóra vagy kérésre jellemzőek. Ez nem ritka követelmény: tranzakciók, biztonsági kontextus vagy más 'globális' adatok közzététele egy helyen ahelyett, hogy minden módszerhíváson keresztül továbbítanák őket, ahogy a tramp-adatok Tisztább (és olvashatóbb) megvalósítás. Viszont remek hely, hogy a lábba (vagy fejbe) lődd magad, ha nem vagy óvatos. Azt hittem, tudom, mi történik, de nem tudtam.
A preferált opció, az egyeslemezek tárolása a HttpContext.Current.Items mappában, egyszerű és biztonságos, de az adott singletont egy ASP.Net alkalmazáson belül való használathoz köti. Ha az egyedülálló a vállalkozásod problémáiban, ez nem ideális. Még akkor is, ha az tulajdonság-hozzáférést if állításba csomagolod
... akkor még mindig a System.Web-re kell hivatkozni az összeállításból, ami általában több 'webby' objektumot foglal be rossz helyre.
Az alternatívák egy [ThreadStatic] statikus tag, a Thread helyi tároló használata (ami nagyjából ugyanaz), vagy a CallContext használata.
A [ThreadStatic] problémái jól dokumentáltak, de összefoglalva: A mezőintellizerek csak az első szálon aktiválódnak A ThreadStatic adatok kifejezetten tisztázásra van szükség (például az EndRequest-ben), mert bár a Thread elérhető, a ThreadStatic adatok nem lesznek GC-z, így lehet, hogy az erőforrásokat szivárogtatod. A ThreadStatic adatok csak egy kérésen belül jók, mert a következő kérés egy másik szálon érkezhet, és megkaphatja valaki más adatait. Scott Hanselman jól érti, hogy a ThreadStatic nem működik jól ASP.Net, de nem magyarázza meg teljesen, miért.
A CallContext-ben tárolt tárolás enyhíti ezeket a problémákat, mivel a kontextus a kérés végén eltűnik, és a GC végül megtörténik (bár az erőforrásokat még mindig kiszivárogtathatod, amíg a GC be nem történik, ha eldobható tárgyakat tárolsz). Ráadásul a CallContext az, ahogyan a HttpContext tárolódik, szóval rendben kell lennie, ugye? Mindegyáltalán, azt gondolnád (ahogy én is), hogy ha minden kérés végén rendet raksz magad után, minden rendben lesz: "Ha a kérés elején inicializálod a ThreadStatic változót, és helyesen kezeled a hivatkozott objektumot a kérés végén, kockáztathatnám, hogy azt állítom, hogy semmi rossz nem fog történni
"Most,Lehet, hogy tévedek. A CLR lehet, hogy félúton abbahagyja a szál hosztolását, valahol soriálissá teheti a stackjét, ad neki egy új stacket, és hagyhatja, hogy elkezdje a futtatást。 Mélyen kétlem ezt. Gondolom, elképzelhető, hogy a hyperthreading is megnehezítené a dolgokat, de ezt kétlem. ”
Jeff Newsom
"Ha egy kérés elején inicializálsz egy ThreadStatic változót, és megfelelően eltünteted a hivatkozott objektumot a kérés végén, akkor vállalom, hogy semmi sem Rossz fog történni. Még az AppDomainben is menő vagy a kontextusok között
"Lehet, hogy tévedek. A clr potenciálisan megállíthatná a menedzselt szálat a stream közepén, soriálissá teheti a stackjét valahol, adhat neki egy új veremet, és hagyhatja, hogy elkezdje a végrehajtást. Komolyan kétlem. Feltételezem, hogy a hiperszálozás is megnehezíti a dolgokat, de ezt is kétlem." Jef Newsom Frissítés: Ez félrevezető. Később részletesebben elmagyarázom, hogy ez a szálcsere csak a BeginRequest és Page_Load között történhet, de Jef hivatkozása egy nagyon erős képet hoz létre, amit azonnal nem tudtam kijavítani.
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. Így egy ponton ASP.NET úgy döntenek, hogy túl sok I/O szál dolgozza fel más kéréseket. […] Csak kéréseket fogad el, és ASP.NET futtatóidőn belül belső sorobjektumban sorba állítja őket. Ezután a sorba állítás után az I/O szál egy worker szálat kér, majd az I/O szál visszatér a saját oldalához. […] Ezért ASP.NET hagyom, hogy az adott dolgozó kezelje a kérést. Ez ASP.NET futásidőre viszi, pont úgy, mint az I/O szál alacsony terhelésnél.
Így egy ponton ASP.NET úgy dönt, hogy túl sok I/O szál dolgozza fel más kéréseket. [...] Egyszerűen csak a kérést veszi fel, és sorba állítja ebben a belső sorobjektumban a ASP.NET futásidőben. Miután ezt sorba állították, az I/O szál worker szálat kér, majd az I/O szál visszakerül a pommjába. [...] Tehát ASP.NET a munkatársi szál feldolgozza a kérést. A játékot a ASP.NET futásidőbe viszi, ahogy az I/O szál alacsony terhelés alatt is tette volna. Mindig is tudtam róla, de azt hiszem, nagyon korán történt, és nem érdekelt. Viszont úgy tűnik, tévedek. Volt egy problémánk ASP.Net alkalmazásban, amikor egy felhasználó rákattintott egy linkre, miután egy másik linkre kattintott, és az alkalmazásunkban null referencia kivétel volt az egyik singletonban (én a singletonhoz a CallContext-et használtam a ThreadStatic helyett, de az nem volt releváns).
Utánanéztem ASP.Net pontosan hogyan működnek a szálak, és ellentmondó véleményeket kaptam, amelyeket tényként álcáztak (a kérések thread-agilis a kérésekben, míg a kérések a szálakon alapulnak az élettartamuk alatt), ezért lemásoltam a problémámat egy tesztalkalmazásban, ahol lassú oldal (egy pillanatra alszik) és gyors oldal. Rákattintok a lassú oldal linkére, és mielőtt az oldal visszatérne, rákattintok a gyors oldal linkére. Az eredmény (log4net dump arról, mi történik) teljesen lenyűgözött.
A kimenet azt mutatja, hogy a második kérésnél a BeginRequest esemény és az oldalépítő a HttpModule csővezetékben az egyik szálon aktiválódik, de page_Load a másikban. A második szál már átmigrálta a HttpContext-t az első szálból, de a CallContext vagy a ThreadStatic nem (megjegyzés: mivel maga a HttpContext a CallContextben van tárolva, ez azt jelenti, hogy ASP.Net kifejezetten migrálja a HttpContextet). Mondjuk még egyszer:
Mindig is tudtam erről, de feltételeztem, hogy elég korán történt a folyamat során, hogy nem érdekel. Úgy tűnik azonban, hogy tévedtem. A ASP.Net alkalmazásunkban már problémánk van, hogy a felhasználó egy linkre kattint közvetlenül a másikra, és az alkalmazásunk null hivatkozási kivételt kap az egyik singletonunkra (én használom CallContext, nem ThreadStatic az egyszemélyes személynél, de kiderült, hogy nem számít).
Kicsit utánanéztem az ASP-nek, pontosan hogyan működik. A Net threading működik, és ellentmondásos véleményeket kaptunk – tényként álcázzák (a kérések egy kérésen belül agilisek, míg a kérések életük alatt egy szálhoz vannak rögzítve), így én is lemásoltam a probléma egy tesztalkalmazásban, ahol lassú oldal (egy pillanatra alszik) és gyors oldal. Rákattintok a lassú oldal linkére, és mielőtt visszatér az oldal, rákattintok a gyors oldal linkére. Az eredmények (egy log4net dump arról, mi történik) megleptek.
A kimenet azt mutatja, hogy a második kérésnél a HttpModule csővezetékben és az oldalépítő BeginRequest eseményei egy szálon aktiválódnak, de a Page_Load a másikon aktiválódik. A második szálhoz a HttpContext az elsőből került át, de a CallContext vagy a ThreadStatic (megjegyzés: mivel maga a HttpContext a CallContext-ben tárolódik, ez azt jelenti ASP.Net explicit migrációval áthelyezve a HttpContext-et). Fejtsük ki újra:
- A szálváltás az IHttpHandler létrehozása után történik
- Az oldal mezőinicializátora és a konstruktor futtatása után
- Bármely BeginRequest, AuthenticateRequest, AquireSessionState típusú esemény után, amelyeket a Global.ASA/IHttpModules használ.
- Csak a HttpContext kerül az új szálba
A szálcsere az IHttpHandler létrehozása után történik Az oldal mezőinicialátorai és a konstruktor futtatása után Minden BeginRequest, AuthenticateRequest, AquireSessionState típusú esemény után, amelyet a Global.ASA / IHttpModules használ. Csak a HttpContext migrál az új szálra Ez komoly macerás, mert amit láttam, azt jelenti, hogy az egyetlen tartóssági opció a "ThreadStatic" osztály viselkedésére ASP.Net-ben az HttpContext használata. Tehát az üzleti objektumaidhoz vagy továbbra is használod az if(HttpContext.Current!) funkciót. =null) és System.Web referencia (fúj), vagy valamilyen szolgáltatói modellt kell kidolgozni a statikus állandósághoz, amit be kell állítani, mielőtt hozzáférnél ezekhez a singletonokhoz. Dupla hányinger.
Kérem, mondja, hogy ez nem így van.
Melléklet: Teljes napló:
Ez komoly macerás, mert amennyire látom, azt jelenti, hogy az egyetlen tartóssági opció a 'ThreadStatic'-szerű viselkedéshez ASP.Net-ben a HttpContext használata. Tehát az üzleti objektumaidnál vagy az if(HttpContext.Current!=null) és a System.Web referencia (fúj) mellett ragadsz, vagy ki kell dolgoznod valamilyen szolgáltatói modellt a saját igényeidhez Statikus tartósság, amelyet be kell állítani, mielőtt bármelyik ilyen singletonhoz hozzáférnének. Dupla fujdonság.
Kérlek, valaki mondja, hogy nem így van.
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(most)=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() - Lassú oldal alszik....
[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(most)=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() - Lassú oldal felébred... [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() - END /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() - VÉGE /ConcurrentRequestsDemo/FastPage.aspx A kulcs az, mi történik, amikor a FastPage Page_Load aktiválódik. A ThreadID 4748, de a ctor HttpContext-ében tárolt ThreadID 2720. A logikai szálak hash kódja 1703, de a ctorban tárolt hash kódom 1835. Az összes adat, amit a CallContextben tároltam, hiányzik (még az ILogicalThreadAffinnatív jelzésű adat is), de a HttpContext még mindig megvan. Ahogy várható, a ThreadStatic is eltűnt.
A kulcs az, mi történik, amikor a FastPage Page_Load elindul. A ThreadID 4748, de a HttpContext-ben tárolt threadID a ctor esetében 2720. A logikai szál hash kódja 1703, de amit a ctorban tároltam, az 1835. Minden adat, amit a CallContext-ben tároltam, eltűnt (még az ILogicalThreadAffinative jelzés is), de a HttpContext még mindig megvan. Ahogy várható, a ThreadStatic is eltűnt. (Vége)
|