Alkuperäinen:Hyperlinkin kirjautuminen on näkyvissä.
Yhteenveto: Vaikka luulisit tietäväsi, mitä teet, ei ole turvallista tallentaa mitään ThreadStatic-jäseneen, CallContextiin tai Thread Localiin ASP.Net-sovelluksessa, jos arvo on asetettu etukäteen. Page_Load (esim. IHttpModulessa tai sivun rakentajassa), mutta käytön aikana tai sen jälkeen.
[Päivitys: elokuu 2008 Koska melko moni jatkaa linkittämistä tähän artikkeliin, koen tarpeelliseksi selventää, että tämä ketjuvaihtokäyttäytyminen tapahtuu hyvin tarkassa vaiheessa sivun elinkaarta, eikä silloin kun siltä tuntuu.] Sanavalintani Jeff Newsomia siteerattuani on valitettavaa. Muutoin olen erittäin iloinen (ja otettu) nähdessäni viittauksia tähän artikkeliin useaan otteeseen suunnittelukeskusteluissa HttpContextin asianmukaisesta käsittelystä. Olen iloinen, että ihmiset kokevat sen hyödylliseksi. ]
On paljon epäselvyyttä siitä, miten käyttäjäkohtaiset singletonit toteutetaan ASP.Net – eli globaali data on globaalia vain yhdelle käyttäjälle tai pyynnölle. Tämä ei ole harvinainen vaatimus: julkaista transaktio-, turvallisuuskonteksti tai muu "globaali" data yhdessä paikassa sen sijaan, että työntäisi ne jokaiseen metodikutsuun, koska loam-data mahdollistaa selkeämmän (ja luettavamman) toteutuksen. Jos et kuitenkaan ole varovainen, tämä on loistava paikka ampua itseäsi jalkaan (tai päähän). Luulin tietäväni, mitä tapahtui, mutta en tiennyt.
Suosituin vaihtoehto on singletonTallennettu HttpContext.Current.Items -tiedostoon, yksinkertainen ja turvallinen, mutta yhdistäen kyseisen singletonin sen käyttöön ASP.Net sovelluksessa. Jos yksittäinen yritys epäonnistuu liiketoimintakohteessasi, tämä ei ole ihanteellista. Vaikka käärittäisit ominaisuuspääsyn if-lauseeseen
Summary: Vaikka luulisit tietäväsi mitä teet, ei ole turvallista tallentaa mitään ThreadStatic-jäseneen, CallContextiin tai Thread Local Storageen ASP.Net-sovelluksessa, jos se on mahdollista että arvo voidaan asettaa ennen Page_Load (esim. IHttpModulessa tai sivun rakentajassa), mutta siihen pääsee käsiksi sen aikana tai sen jälkeen.
[Päivitys: elokuu 2008 Koska melko suuri määrä ihmisiä linkittää tähän postaukseen, koen tarpeelliseksi selventää, että tämä ketjujen vaihto tapahtuu hyvin tietyssä kohdassa sivua Elinkaari eikä silloin kun siltä tuntuu. Sanavalintani Jef Newsonin lainauksen jälkeen oli valitettavaa. Siitä huolimatta olen ollut valtavan tyytyväinen (ja imarreltu) siitä, kuinka monta kertaa olen nähnyt tämän kirjoituksen siteerattavan suunnittelukeskusteluissa HttpContextin asianmukaisesta käsittelystä. Olen iloinen, että ihmiset pitivät sitä hyödyllisenä.]
On paljon sekaannusta siitä, miten käyttäjäkohtaisia singleton-tiedostoja toteutetaan ASP.Net – eli globaalia dataa, joka on globaalia vain yhdelle käyttäjälle tai pyynnölle. Tämä ei ole harvinainen vaatimus: Transaktioiden, tietoturvakontekstin tai muun 'globaalin' datan julkaiseminen yhdessä paikassa sen sijaan, että se työntäisi jokaisen metodikutsun läpi kuten tramp-data, voi tehdä puhtaampi (ja helpommin luettava) toteutus. Se on kuitenkin loistava paikka ampua itseään jalkaan (tai päähän), jos ei ole varovainen. Luulin tietäväni, mitä tapahtui, mutta en tiennyt.
Suosituin vaihtoehto, yksittäisten tiedostojen tallentaminen HttpContext.Current.Items -tiedostoon, on yksinkertainen ja turvallinen, mutta yhdistää kyseisen singletonin käytettäväksi ASP.Net-sovelluksessa. Jos yksittäinen henkilö on alhaalla yrityksesi esineissä, tämä ei ole ihanteellista. Vaikka käärittäisit property-access if-lauseeseen
... silloin sinun täytyy silti viitata System.Webiin kyseisestä assemblystä, joka yleensä kerää enemmän 'webby'-objekteja väärään paikkaan.
Vaihtoehdot ovat käyttää [ThreadStatic]-staattista jäsentä, Thread-paikallista tallennusta (joka on käytännössä sama asia) tai CallContext.
[ThreadStaticin] ongelmat on hyvin dokumentoitu, mutta tiivistettynä: Kenttä-italisaattorit toimivat vain ensimmäisellä langalla ThreadStatic-data vaatii selkeää siivousta (esim. EndRequestissa), koska vaikka Thread on saavutettavissa, ThreadStatic-data ei ole GC:llä, joten saatat vuotaa resursseja. ThreadStatic-data on hyödyllinen vain pyynnössä, koska seuraava pyyntö voi tulla eri säikeessä ja saada jonkun toisen dataa. Scott Hanselman on oikeassa, että ThreadStatic ei toimi hyvin ASP.Net kanssa, mutta ei täysin selitä miksi.
Tallennus CallContextissa helpottaa joitakin näistä ongelmista, koska konteksti kuolee pyynnön lopussa ja GC tapahtuu lopulta (vaikka voit silti vuotaa resursseja siihen asti, kunnes GC tapahtuu, jos säilytät kertakäyttöisiä). Lisäksi CallContext on tapa, jolla HttpContext tallennetaan, joten sen täytyy olla ok, eikö niin? Joka tapauksessa voisi ajatella (kuten minä tein), että jos siivoat jälkesi jokaisen pyynnön lopussa, kaikki olisi kunnossa: "Jos alustat ThreadStatic-muuttujan pyynnön alussa ja käsittelet oikein viitatun objektin pyynnön lopussa, riskeeraisin väittämisen, ettei mitään pahaa tapahdu
"Nyt,Voin olla väärässä. CLR saattaa lopettaa säikeen isännöinnin puolivälissä, sarjoittaa pinon jonnekin, antaa sille uuden pinon ja antaa sen alkaa suorittaa。 Epäilen tätä syvästi. On kai mahdollista, että hyperthreading tekisi asioista vaikeita, mutta epäilen sitä. ”
Jeff Newsom
"Jos alustat ThreadStatic-muuttujan pyynnön alussa ja hävität asianmukaisesti viitatun objektin pyynnön lopussa, uskallan väittää, ettei mitään Pahaa tulee tapahtumaan. Olet jopa cool olla kontekstien välillä samassa AppDomainissa
"Nyt, voin olla väärässä tässä. CLR voisi mahdollisesti pysäyttää hallitun säikeen kesken virran, serialisoida sen pinon jonnekin, antaa sille uuden pinon ja antaa sen alkaa suorittaa. Epäilen sitä vakavasti. Oletan, että on mahdollista, että hyperthreading tekee asioista myös vaikeita, mutta epäilen sitäkin." Jef Newsom Päivitys: Tämä on harhaanjohtavaa. Selitän myöhemmin lisää, että tämä ketjun vaihto voi tapahtua vain BeginRequestin ja Page_Load:n välillä, mutta Jefin viittaus luo erittäin voimakkaan kuvan, jota en onnistunut korjaamaan heti.
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. Jossain vaiheessa ASP.NET päätän, että I/O-säikeitä on liikaa käsittelemässä muita pyyntöjä. […] Se hyväksyy vain pyynnöt ja asettaa ne jonoon sisäiseen jonoobjektiin ASP.NET ajonajan sisällä. Sitten jonon jälkeen I/O-säie pyytää työntekijäsäiettä, ja I/O-säie palaa omaan pooliinsa. […] Siksi ASP.NET antaa kyseisen työntekijän hoitaa pyynnön. Se tuo sen ASP.NET ajonaikaan, aivan kuten I/O-säie tekisi matalalla kuormituksella.
Jossain vaiheessa ASP.NET päättää, että I/O-säikeitä käsittelee liikaa muita pyyntöjä. [...] Se vain ottaa pyynnön ja asettaa sen jonoon tähän sisäiseen jonoobjektiin ASP.NET ajonaikaan. Sitten, kun se on jonossa, I/O-säie pyytää työntekijäsäiettä, ja I/O-säie palautetaan omaan pooliinsa. [...] Joten ASP.NET saa työntekijäsäikeen käsittelemään pyynnön. Se ottaa sen ASP.NET ajonaikaan, aivan kuten I/O-säie olisi tehnyt matalalla kuormituksella. Olen aina tiennyt siitä, mutta luulen, että se tapahtui hyvin varhain enkä välittänyt. Kuitenkin vaikuttaa siltä, että olen väärässä. Meillä oli ongelma ASP.Net sovelluksessa, jossa käyttäjä klikkasi linkkiä toisen linkin jälkeen, ja sovelluksessamme oli null reference -poikkeus yhdessä singletonista (käytin CallContextia ThreadStaticin sijaan, mutta se osoittautui merkityksettömäksi).
Tein hieman tutkimusta siitä, miten ASP.Net säikeet tarkalleen toimivat, ja sain ristiriitaisia mielipiteitä, jotka naamioitiin faktoiksi (pyynnöt ovat säikeiden ketteriä pyynnöissä, kun taas pyynnöt ankkuroituvat säikeisiin niiden elinkaaren ajan), joten kopioin ongelmani testisovellukseen, jossa sivu oli hidas (nukkui hetken) ja nopea sivu. Klikkaan linkkiä hitaalle sivulle, ja ennen kuin sivu palaa, klikkaan linkkiä nopealle sivulle. Tulos (log4net-purkaus siitä, mitä tapahtuu) räjäytti tajuntani.
Tuloste osoittaa, että toisessa pyynnössä BeginRequest-tapahtuma ja sivun rakentaja HttpModule-putkessa aktivoituvat yhdessä säikeessä, mutta page_Load toisessa. Toinen säie on jo siirtänyt HttpContextin ensimmäisestä säikeestä, mutta ei CallContextia tai ThreadStatiikkaa (huom: koska HttpContext itsessään on tallennettu CallContextiin, ASP.Net siirtää HttpContextia eksplisiittisesti). Sanotaan se uudestaan:
Olen aina tiennyt tästä, mutta oletin, että se tapahtui tarpeeksi aikaisin prosessissa, etten välittänyt. Näyttää kuitenkin siltä, että olin väärässä. Meillä on ollut ongelma ASP.Net-sovelluksessamme, jossa käyttäjä klikkaa yhtä linkkiä heti seuraavan jälkeen, ja sovelluksemme räjähtää null-viitepoikkeuksella yhdelle singletonistamme (minä käytän CallContext ei ThreadStatic singletonille, mutta sillä ei ole väliä).
Tein hieman tutkimusta siitä, miten ASP tarkalleen ottaen toimii. Netin säikeiden käyttö toimii, ja sain ristiriitaisia mielipiteitä, jotka esiintyvät faktoina (pyynnöt ovat säikeiden ketteriä pyynnön sisällä, kun taas pyynnöt kiinnitetään säikeeseen eliniän ajaksi), joten replikoin omani ongelma testisovelluksessa, jossa sivu on hidas (nukkuu hetken) ja nopea sivu. Klikkaan hitaan sivun linkkiä ja ennen kuin sivu palaa, klikkaan linkkiä nopealle sivulle. Tulokset (log4net-dump siitä, mitä tapahtuu) yllättivät minut.
Tuloste näyttää, että toisessa pyynnössä BeginRequest-tapahtumat HttpModule-putkessa ja sivun rakentaja aktivoituvat yhdessä säikeessä, mutta Page_Load käynnistyy toisessa. Toisessa säikeessä HttpContext on siirretty ensimmäisestä, mutta CallContext tai ThreadStatic ei ole siirretty (huom: koska HttpContext itsessään on tallennettu CallContextiin, tämä tarkoittaa, että ASP.Net on eksplisiittisesti siirtäen HttpContextin toiselle puolelle). Selvennetäänpä tämä uudelleen:
- Säikeiden vaihto tapahtuu IHttpHandlerin luomisen jälkeen
- Sivun kentän alustajan ja konstruktorin jälkeen
- Minkä tahansa BeginRequest-, AuthenticateRequest- ja AquireSessionState-tyyppisten tapahtumien jälkeen, joita Global.ASA/IHttpModules käyttää.
- Vain HttpContext siirretään uuteen säikeeseen
Säikekytkin tapahtuu sen jälkeen, kun IHttpHandler on luotu Sivun kentän alustajien ja konstruktorin suorituksen jälkeen Minkä tahansa BeginRequest-, AuthenticateRequest- ja AquireSessionState-tyyppisten tapahtumien jälkeen, joita Global.ASA / IHttpModules käyttävät. Vain HttpContext siirtyy uuteen säikeeseen Tämä on iso riesa, koska näkemäni perusteella ainoa pysyvyysvaihtoehto "ThreadStatic"-luokan käyttäytymiselle ASP.Net on käyttää HttpContextia. Eli liiketoimintaobjekteillesi joko jatkat if(HttpContext.Current!) käyttöä. =null) ja System.Web-viitteen (yäk), tai sinun täytyy keksiä jonkinlainen palveluntarjoajamalli staattiselle pysyvyydelle, joka täytyy asettaa ennen näiden singletonien käyttöä. Kaksoispahoinvointi.
Sanokaa, ettei näin ole.
Liite: Koko loki:
Tämä on iso riesa, koska minun näkemykseni mukaan ainoa 'ThreadStatic'-tyyppisen käyttäytymisen pysyvyysvaihtoehto ASP.Net on käyttää HttpContextia. Joten liiketoimintaobjekteillesi joko olet jumissa if(HttpContext.Current!=null) ja System.Web-viitekehykseen (yäk) tai sinun täytyy keksiä jonkinlainen palveluntarjoajamalli staattinen pysyvyys, joka vaatii käyttöönottoa ennen kuin näitä yksittäisiä yksilöitä päästään käyttöön. Kaksinkertainen yäk.
Ole kiltti, joku sano, ettei se ole totta.
Appendix: That log in full: [3748] INFO 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - ALOITA /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() - Hidas sivu nukkuu....
[2720] INFO 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - ALOITA /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() - Hidas sivu herää.... [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() - LOPETA /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() - LOPETA /ConcurrentRequestsDemo/FastPage.aspx Avain on, mitä tapahtuu, kun FastPagen Page_Load laukeaa. ThreadID on 4748, mutta ctorin HttpContextiin tallennettu ThreadID on 2720. Loogisten säikeiden hajautuskoodi on 1703, mutta ctorissa tallentamani hajautuskoodi on 1835. Kaikki CallContextiin tallentamani data puuttuu (jopa ILogicalThreadAffinnative-merkitty data), mutta HttpContext on yhä siellä. Kuten voi odottaa, myös ThreadStaticini on poissa.
Tärkeintä on, mitä tapahtuu, kun FastPagen Page_Load käynnistyy. ThreadID on 4748, mutta ThreadID, jonka tallennin HttpContextiin ctoriin, on 2720. Loogisen säikeen hajautuskoodi on 1703, mutta se, jonka tallensin ctoriin, on 1835. Kaikki data, jonka tallennin CallContextiin, on poissa (myös se, joka on merkitty ILogicalThreadAffinative), mutta HttpContext on yhä siellä. Kuten odottaa saattaa, myös ThreadStaticini on poissa. (Loppu)
|