Originaal:Hüperlingi sisselogimine on nähtav.
Kokkuvõte: Isegi kui arvad, et tead, mida teed, ei ole turvaline salvestada midagi ThreadStatiku liikmesse, CallContexti või Thread Local poodi ASP.Net rakenduses, kui väärtus on eelnevalt seadistatud. Page_Load (nt IHttpModule'is või lehekonstruktoris), kuid juurdepääsu ajal või pärast seda.
[Uuendus: august 2008 Arvestades, et päris paljud inimesed jätkavad selle artikli linkimist, pean vajalikuks selgitada, et see teemavahetuse käitumine toimub lehe elutsükli väga kindlal hetkel, mitte siis, kui see tundub nii.] Minu sõnastus pärast Jeff Newsomi tsiteerimist on ebaõnnestunud. Muus osas olen väga rõõmus (ja meelitatud), nähes selle artikli viiteid korduvalt disainiaruteludes HttpContexti korrektse käsitlemise teemal. Mul on hea meel, et inimesed leiavad selle kasulikuks. ]
On palju segadust selle osas, kuidas rakendada kasutajaspetsiifilisi üksikuid andmeid ASP.Net – see tähendab, et globaalandmed on globaalsed ainult ühe kasutaja või päringu jaoks. See pole haruldane nõue: avaldada tehingupõhised, turvakontekst või muud "globaalsed" andmed ühes kohas, mitte suruda need igasse meetodikutsesse, sest saviandmed võimaldavad selgemat (ja loetavamat) rakendust. Kui sa aga ettevaatlik ei ole, on see suurepärane koht, kus endale jalga (või pähe) lasta. Ma arvasin, et tean, mis toimub, aga ei teadnud.
Eelistatud valik on singletonSalvestatud aadressil httpContext.Current.Items, lihtne ja turvaline, kuid seostades nimetatud singletoni selle kasutamisega ASP.Net rakenduses. Kui üksikpank ebaõnnestub sinu äriobjektis, siis see pole ideaalne. Isegi kui mähid omaduse juurdepääsu if-lausesse
Summary: Isegi kui arvad, et tead, mida teed, ei ole turvaline salvestada midagi ThreadStatic-liikmesse, CallContexti või Thread Local Storage'i ASP.Net rakenduses, kui see on võimalik et väärtus võidakse seadistada enne Page_Load (nt IHttpModule'is või leheküljekonstruktoris), kuid sellele pääseb ligi kas ajal või hiljem.
[Uuendus: august 2008 Arvestades üsna suurt hulka inimesi, kes sellele postitusele viitavad, tunnen vajadust selgitada, et see teemade vahetamise käitumine toimub lehe väga kindlal hetkel elutsükkel, mitte siis, kui tahad. Minu sõnastus pärast Jef Newsoni tsitaati oli ebaõnnestunud. Sellest hoolimata olen olnud tohutult rahul (ja meelitatud), kui palju kordi olen näinud seda postitust disainiaruteludes tsiteerituna HttpContexti asjakohase käsitlemise kohta. Mul on hea meel, et inimesed leidsid selle kasulikuks.]
On palju segadust kasutajaspetsiifiliste üksikute failide rakendamise osas ASP.Net – see tähendab globaalseid andmeid, mis on globaalsed ainult ühele kasutajale või päringule. See pole haruldane nõue: tehingute, turvakonteksti või muu 'globaalsete' andmete avaldamine ühes kohas, selle asemel et suruda neid läbi iga meetodikutse nagu tramp-andmed võivad tekitada puhtam (ja loetavam) teostus. Kuid see on suurepärane koht, kus endale jalga (või pähe) lasta, kui sa pole ettevaatlik. Ma arvasin, et tean, mis toimub, aga ei teadnud.
Eelistatud variant, salvestada üksikud failid HttpContext.Current.Items faili, on lihtne ja turvaline, kuid seob konkreetse üksiku faili kasutamisega ASP.Net rakenduses. Kui üksik isik on sinu äri vastu, pole see ideaalne. Isegi kui mähid omaduse ligipääsu if-lausesse
... siis pead ikkagi viitama System.Webile sellest assambleest, mis kipub rohkem 'webby' objekte valesse kohta koondama.
Alternatiivid on kasutada [ThreadStatic] staatilist liiget, Thread lokaalset salvestust (mis on sisuliselt sama asi) või CallContexti.
[ThreadStatic] probleemid on hästi dokumenteeritud, kuid kokkuvõtteks: Väli-italisarid töötavad ainult esimesel lõimel ThreadStatic andmed vajavad selget puhastamist (nt EndRequestis), sest kuigi Thread on ligipääsetav, ei ole ThreadStaticu andmed GC-ga, seega võib ressursse lekkida. ThreadStatic andmed on kasulikud ainult päringu sees, sest järgmine päring võib tulla teisest lõimest ja saada kellegi teise andmed. Scott Hanselman saab õigesti aru, et ThreadStatic ei sobi ASP.Net-ga hästi, kuid ei selgita täielikult, miks.
Salvestus CallContextis leevendab mõningaid neist probleemidest, kuna kontekst sureb päringu lõpus ja GC toimub lõpuks (kuigi ressursse saab ikkagi lekkida kuni GC-ni, kui sa hoiad ühekordseid esemeid). Lisaks on CallContext see, kuidas HttpContext salvestatakse, seega peab see olema okei, eks?. Igatahes võiks arvata (nagu mina), et kui sa iga palve lõpus enda järel koristad, on kõik korras: "Kui sa initsialiseerid ThreadStatic muutuja päringu alguses ja käsitled viidatud objekti õigesti päringu lõpus, riskiksin väita, et midagi halba ei juhtu
"Nüüd,Võin eksida. CLR võib lõpetada lõime hostimise poole pealt, serialiseerida selle virna kuskil, anda sellele uue virna ja lasta sellel hakata täitma。 Ma kahtlen selles sügavalt. Arvan, et on mõeldav, et hüperlõimimine teeks asjad keeruliseks, aga ma kahtlen selles. ”
Jeff Newsom
"Kui sa initsialiseerid ThreadStatic muutuja päringu alguses ja kõrvaldad viidatud objekti päringu lõpus korrektselt, siis ma julgen väita, et mitte midagi Halb juhtub. Sa oled isegi sama AppDomaini kontekstide vahel lahe
"Nüüd, ma võin eksida. CLR võiks potentsiaalselt peatada hallatud lõime keset voogu, seriaaliseerida selle virna kuskile, anda sellele uue virna ja lasta sellel hakata täitma. Ma kahtlen selles tõsiselt. Ma arvan, et on mõeldav, et hüperlõimimine teeb asjad samuti keeruliseks, aga ma kahtlen selles." Jef Newsom Uuendus: See on eksitav. Selgitan hiljem veel, et see teemavahetus saab toimuda ainult BeginRequesti ja Page_Load vahel, kuid Jefi viide loob väga võimsa pildi, mida ma kohe ei parandanud.
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. Nii et mingil hetkel otsustan ASP.NET, et liiga palju I/O lõime, mis töötlevad teisi päringuid. […] See aktsepteerib ainult päringuid ja paneb need järjekorda sisemises järjekorraobjektis ASP.NET käitusaja jooksul. Pärast järjekorda panemist taotleb I/O lõim töölõime ja I/O lõim naaseb oma basseini. […] Seetõttu laseb ASP.NET sellel töötajal päringu lahendada. See viib selle ASP.NET käitusaega, täpselt nagu I/O lõim madala koormuse korral.
Nii et mingil hetkel otsustab ASP.NET, et liiga palju I/O lõime, mis töötlevad teisi päringuid. [...] See lihtsalt võtab päringu ja paneb selle järjekorda sellesse sisemisse järjekorraobjekti ASP.NET käitusajas. Seejärel, kui see on järjekorda pandud, küsib I/O lõim töötajalõime ja I/O lõim tagastatakse oma basseini. [...] Nii et ASP.NET laseb sellel töötaja lõimel päringut töödelda. See võtab selle ASP.NET käitusaega, täpselt nagu I/O lõim madala koormuse korral. Ma teadsin sellest alati, aga arvan, et see juhtus väga vara ja mind ei huvitanud. Kuid tundub, et eksin. Meil oli ASP.Net rakenduses probleem, kus kasutaja klõpsas lingil pärast teisele lingile klikkimist ja meie rakenduses oli ühes üksikus rakenduses null viite erand (kasutasin üksikkasutaja jaoks CallContexti ThreadStatiku asemel, aga see osutus ebaoluliseks).
Uurisin täpselt, kuidas ASP.Net lõimed töötavad, ja sain vastuolulisi arvamusi, mis olid maskeeritud faktidena (päringud on päringutes lõime-agiilsed, samas kui päringud on kogu eluea jooksul lõimede külge ankurdatud), nii et kopeerisin oma probleemi testrakendusesse, kus leht oli aeglane (hetkeks magama jäänud) ja kiire leht. Klõpsan aeglase lehe lingil ja enne kui leht tagasi tuleb, klõpsan kiire lehe lingil. Tulemus (log4net dump sellest, mis toimub) üllatas mind.
Väljund näitab, et teise päringu puhul töötavad BeginRequest sündmus ja lehe konstruktor HttpModule'i torujuhtmes ühel lõimel, kuid page_Load teises. Teine lõim on juba migreerinud HttpContexti esimesest lõimest, kuid mitte CallContexti ega ThreadStaticut (märkus: kuna HttpContext ise on salvestatud CallContexti, tähendab see, et ASP.Net migreerib HttpContexti selgesõnaliselt). Ütleme seda veel kord:
Ma teadsin sellest alati, aga arvasin, et see juhtus piisavalt varakult, et mind ei huvita. Tundub siiski, et eksisin. Meil on olnud probleem meie ASP.Net rakenduses, kus kasutaja klõpsab ühel lingil kohe pärast teisele klõpsamist ja meie rakendus plahvatab nullviite erandiga ühe üksiku kasutaja jaoks (mida kasutan CallContext, mitte ThreadStatic üksiku jaoks, aga selgub, et see pole oluline).
Uurisin natuke, kuidas täpselt ASP. Neti lõimimine töötab ja tekib vastuolulisi arvamusi, mis maskeeruvad faktina (päringud on päringu sees lõime-agiilsed, taotlused on kogu elu jooksul lõimele kinnitatud), nii et ma replikeerisin oma probleem testrakenduses, kus leht on aeglane (magab sekundiks) ja kiire leht. Klõpsan aeglase lehe lingil ja enne kui leht tagasi tuleb, klõpsan kiire lehe lingil. Tulemused (log4net ülevaade toimuvast) üllatasid mind.
Väljund näitab, et teise päringu puhul käivitavad BeginRequest sündmused HttpModuli torujuhtmes ja lehe konstruktor ühes lõimes, kuid Page_Load käivitub teises. Teise lõime puhul on HttpContext esimesest üle viidud, kuid mitte CallContext ega ThreadStatic (märkus: kuna HttpContext ise on salvestatud CallContexti, tähendab see, ASP.Net on HttpContexti selgesõnaliselt üle migreerides). Selgitame selle veel kord:
- Lõime vahetamine toimub pärast IHttpHandleri loomist
- Pärast lehe välja initsialisaatori ja konstruktori käivitamist
- Pärast kõiki BeginRequest, AuthenticateRequest, AquireSessionState tüüpi sündmusi, mida kasutavad Global.ASA/IHttpModules.
- Uude lõime migreeritakse ainult HttpContext
Lõimevahetus toimub pärast IHttpHandleri loomist Pärast lehe välja initsialiseerijate ja konstruktori käivitamist Pärast kõiki BeginRequest, AuthenticateRequest ja AquireSessionState tüüpi sündmusi, mida teie Global.ASA / IHttpModules kasutab. Ainult HttpContext migreerub uude lõime. See on suur peavalu, sest minu kogemuse põhjal tähendab see, et ainus püsivusvõimalus "ThreadStatic" klassi käitumise jaoks ASP.Net on kasutada HttpContexti. Nii et äriobjektide puhul kasuta kas if(HttpContext.Current!). =null) ja System.Webi viide (õudne), või pead välja töötama mingi staatilise püsivuse pakkujamudeli, mis tuleb seadistada enne nende üksikute kasutajate ligipääsu. Topeltiiveldus.
Palun öelge, et see pole nii.
Lisa: Täielik logi:
See on suur tülikas, sest minu arvates tähendab see ainus püsivusvalik 'ThreadStatic'-laadse käitumise jaoks ASP.Net HttpContexti kasutamine. Nii et äriobjektide puhul pead kas kinni if(HttpContext.Current!=null) ja System.Webi viitega (õudne) või pead välja mõtlema mingi teenusepakkuja mudeli oma jaoks staatiline püsivus, mis tuleb seadistada enne, kui ükskõik milline neist üksikutest on ligipäästud. Topelt vastik.
Palun keegi ütle, et see pole nii.
Appendix: That log in full: [3748] INFO 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - ALUSTA /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() - Aeglane lehekülg magab....
[2720] INFO 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - ALUSTA /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() - Aeglane lehekülg ärkab.... [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() - LÕPP /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() - LÕPP /ConcurrentRequestsDemo/FastPage.aspx Oluline on see, mis juhtub, kui FastPage'i Page_Load aktiveerub. ThreadID on 4748, kuid ThreadID I, mis on salvestatud ctori HttpContexti, on 2720. Loogiliste lõimede räsikood on 1703, kuid ctoris salvestatud räsikood on 1835. Kõik andmed, mis ma CallContexti salvestasin, on puudu (isegi andmed, mis on märgitud ILogicalThreadAffinnative), kuid HttpContext on endiselt olemas. Nagu arvata võib, on minu ThreadStatic samuti kadunud.
Oluline on see, mis juhtub, kui FastPage'i Page_Load käivitub. ThreadID on 4748, kuid ThreadID, mille salvestasin HttpContexti ctoris, on 2720. Loogilise lõime räsikood on 1703, kuid see, mille salvestasin ctorisse, on 1835. Kõik andmed, mida ma CallContexti salvestasin, on kadunud (isegi see, mis oli märgitud ILogicalThreadAffinative), kuid HttpContext on endiselt olemas. Nagu arvata võib, on ka minu ThreadStatic kadunud. (Lõpp)
|