Özgün:Bağlantı girişi görünür.
Özet: Ne yaptığınızı bildiğinizi düşünseniz bile, değer önceden ayarlanmış olabilirse, ASP.Net uygulamada ThreadStatic üyesi, CallContext veya Thread Local mağazasında hiçbir şeyi depolamak güvenli değildir. Page_Load (örneğin IHttpModule veya sayfa oluşturucuda), ancak erişim sırasında veya sonrasında olabilir.
[Güncelleme: Ağustos 2008 Birçok kişi bu makaleye bağlantı vermeye devam ettiğini göz önüne alındığında, bu konu değiştirme davranışının sayfanın yaşam döngüsünün çok belirli bir noktasında gerçekleştiğini ve bunun istediği anda değil, netleştirmenin gerekli olduğunu düşünüyorum.] Jeff Newsom'dan alıntı yaptıktan sonra kullandığım sözler talihsiz. Bunun dışında, HttpContext'in doğru şekilde işlenmesiyle ilgili tasarım tartışmalarında bu makaleye defalarca referans verilmesini görmek beni çok mutlu (ve onurlandırıyor). İnsanların faydalı bulmasına sevindim. ]
ASP.Net'de kullanıcıya özgü tekli verilerin nasıl uygulanacağı konusunda çok fazla kafa karışıklığı vardır - yani küresel veri sadece bir kullanıcı veya talep için küreseldir. Bu nadir bir gereklilik değildir: işlemsel, güvenlik bağlamı veya diğer "küresel" verileri tek bir yerde yayımlayın, her yöntem çağrısına itmek yerine, çünkü loam veri daha net (ve daha okunabilir) bir uygulama sağlar. Ancak dikkatli olmazsanız, burası kendinizi ayağınızdan (veya kafanızdan) vurmak için harika bir yer. Ne olduğunu bildiğimi sanıyordum ama bilmiyordum.
Tercih edilen seçenek singleton olacaktırHttpContext.Current.Items içinde saklanıyor, basit ve güvenli, ancak söz konusu singleton'u ASP.Net uygulamasında kullanımıyla ilişkilendiriyor. Eğer tekli iş hedefinizde başarısız olursa, bu ideal değildir. Property erişimini if ifadesine sarsanız bile
Summary: Ne yaptığınızı bildiğinizi düşünseniz bile, bir ASP.Net uygulamasında ThreadStatic üyesi, CallContext veya Thread Local Storage'da herhangi bir şeyi depolamak güvenli değildir, eğer mümkünse değerin Page_Load öncesinde (örneğin IHttpModule veya sayfa oluşturucuda) kurulmuş olabileceği, ancak bu süre sırasında veya sonrasında erişilebileceğini belirtir.
[Güncelleme: Ağustos 2008 Bu gönderiye bağlantı vermeye devam eden oldukça fazla kişi göz önüne alındığında, bu konu değiştirme davranışının sayfanın çok belirli bir noktasında gerçekleştiğini netleştirmem gerektiğini hissediyorum. yaşam döngüsü ve istediğin zaman değil. Jef Newson'dan alıntıdan sonraki iyim, talihsizdi. Bunun dışında, HttpContext ile uygun şekilde başa çıkma konusundaki tasarım tartışmalarında bu gönderinin sayısına çok fazla atıfta bulunduğuma son derece memnun oldum (ve onurlandırıldım). İnsanların faydalı bulmasına sevindim.]
ASP.Net içinde kullanıcıya özgü tekli verilerin nasıl uygulanacağı konusunda çok fazla kafa karışıklığı var - yani sadece bir kullanıcıya veya talebe özgü küresel veri. Bu nadir bir gereklilik değildir: İşlemleri, güvenlik bağlamını veya diğer 'küresel' verileri tek bir yerde yayımlamak, her yöntem çağrısından geçmekten, çünkü bu tür bir şey yaratabilir. daha temiz (ve daha okunabilir) bir uygulama. Ancak, dikkatli olmazsanız kendinizi (veya kafanıza) vurmak için harika bir yer. Ne olduğunu bildiğimi sanıyordum ama bilmiyordum.
Tercih edilen seçenek, singletonlarınızı HttpContext.Current.Items içinde depolamak basit ve güvenlidir, ancak ilgili singletonun ASP.Net bir uygulamada kullanılmasına bağlanır. Eğer bekar işletmenizde sorun çıkarıyorsa, bu ideal değildir. Property-erişimi if ifadesine sarsanız bile
... o zaman yine de System.Web'e o montajdan referans vermeniz gerekir, ki bu da daha fazla 'webby' nesneyi yanlış yere yerleştirir.
Alternatifler ise [ThreadStatic] statik bir üye, Thread yerel depolama (ki bu neredeyse aynı şeye denk geliyor) veya CallContext kullanmaktır.
[ThreadStatic] ile ilgili sorunlar iyi belgelenmiş, ancak özetle: Alan initalerleri sadece ilk iplikte ateşlenir ThreadStatic verileri açıkça temizlenmesi gerekiyor (örneğin EndRequest'te), çünkü Thread ulaşılabilir olsa da, ThreadStatic verileri GC ile belirlenmez, bu yüzden kaynakları sızdırıyor olabilirsiniz. ThreadStatic veri sadece bir istek içinde iyidir, çünkü sonraki istek farklı bir iş parçacığından gelebilir ve başkasının verilerini alabilir. Scott Hanselman doğru söylüyor, ThreadStatic ASP.Net ile iyi uyum sağlamıyor ama nedenini tam olarak açıklamıyor.
CallContext'te depolama bu sorunların bir kısmını hafifletiyor, çünkü bağlam talebin sonunda ölür ve GC sonunda gerçekleşir (ancak GC gerçekleşene kadar kaynakları yine de sızdırabilirsiniz tek kullanımlık ürünleri depolıyorsunuz). Ayrıca CallContext HttpContext'in saklanma şeklidir, yani iyi olmalı, değil mi?. Hiçbir şeye rağmen, (benim gibi) her talebin sonunda kendinden sonra temizlik yaparsanız her şeyin sorun olmayacağını düşünebilirsiniz: "Eğer ThreadStatic değişkenini isteğin başında başlatıp isteğin sonunda referans verilen nesneyi doğru şekilde yönetirseniz, kötü bir şey olmayacağını iddia etme riskini alırım
"Şimdi,Yanılıyor olabilirim. CLR, iş parçacığını yarı yolda barındırmayı bırakabilir, yığını bir yerde seri hale getirebilir, yeni bir yığın verebilir ve çalıştırmaya başlamasına izin verebilir。 Buna kesinlikle şüphe ediyorum. Hyperthreading'in işleri zorlaştırması da mümkün olabilir ama buna da şüphe ediyorum. ”
Jeff Newsom
"Bir isteğin başında bir ThreadStatic değişkenini başlatırsanız ve başvurun sonunda referans edilen nesneyi doğru şekilde ortadan kaldırırsanız, bir şey olmadığını iddia edeceğim Kötü işler olacak. Aynı AppDomain'de bağlamlar arasında bile sorun yok
"Şimdi, yanılıyor olabilirim. Clr, yönetilen bir iş parçacığını akış ortasında durdurabilir, yığınını bir yerde seri olarak çıkarabilir, yeni bir yığın verebilir ve çalışmaya başlamasına izin verebilir. Buna ciddi şekilde şüpheliyim. Hyperthreading'in işleri zorlaştırması da düşünülebilir ama buna da şüpheyle yaklaşıyorum." Jef Newsom Güncelleme: Bu yanıltıcı. Daha sonra daha fazla açıklayacağım, bu konu değişikliği sadece BeginRequest ile Page_Load arasında gerçekleşebilir, ama Jef'in referansı çok güçlü bir görüntü yaratıyor ve hemen düzeltemedim.
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. Bu yüzden, bir noktada ASP.NET diğer istekler işleyen çok fazla I/O iş parçacığı olduğuna karar veriyor. […] Sadece istekleri kabul eder ve ASP.NET çalışma zamanı içinde dahili bir kuyruk nesnesinde sıraya alır. Sonra, sıraya girdikten sonra, G/Ç iş parçacığı bir işçi iş parçacığı talep eder ve ardından G/Ç iş yaşığı havuzuna geri döner. […] Bu yüzden ASP.NET isteği o çalışanın yürütmesine izin verecek. Düşük yükte I/O iş parçacığında olduğu gibi, ASP.NET çalışma zamanına getiriyor.
Bir noktada ASP.NET, diğer istekler üzerinde çok fazla G/Ç iş parçacığı olduğuna karar veriyor. [...] Sadece isteği alır ve ASP.NET çalışma zamanında bu iç kuyruk nesnesine sıraya atar. Sonra, bu sıraya konduktan sonra, G/Ç iş parçacığı bir işçi iş parçacığı isteyecek ve ardından G/Ç iş parçacığı havuzuna geri döner. [...] Yani ASP.NET işçi iş parçacığı talebi işliyor. Düşük yük altında I/O iş akışının olduğu gibi, ASP.NET çalışma zamanına da dahil olur. Ben hep biliyordum ama sanırım çok erken oldu ve umurumda değildi. Ama yanılıyorum gibi görünüyorum. Bir uygulamada ASP.Net sorun yaşadık; bir kullanıcı başka bir bağlantıya tıkladıktan sonra bir bağlantıya tıkladı ve bizim uygulamamızda singletonlardan birinde null referans istisnası vardı (singleton için ThreadStatic yerine CallContext kullandım, ama bu alakasız çıktı).
ASP.Net iş başlıklarının tam olarak nasıl çalıştığı hakkında biraz araştırma yaptım ve çelişkili görüşler aldım (istekler isteklerde thread-agile, istekler ise ömür boyu iş başlıklarına sabitlenir), bu yüzden sorunumu yavaş sayfa (bir saniye uyku süresi var) ve hızlı sayfa olan bir test uygulamasında kopyaladım. Yavaş sayfanın bağlantısına tıklıyorum ve sayfa geri dönmeden önce hızlı sayfanın bağlantısına tıklıyorum. Sonuç (olanları log4net olarak anlatmak) aklımı başımdan aldı.
Çıktı, ikinci istek için HttpModule boru hattındaki BeginRequest olayı ve sayfa oluşturucunun bir iş parçacığında ateşlendiğini, ancak başka bir iş parçacığında page_Load olduğunu gösterir. İkinci iş parçacığı, CallContext veya ThreadStatic'i zaten ilk iş parçacığından HttpContext olarak taşımış olabilir ama CallContext veya ThreadStatic'i taşımamıştır (not: HttpContext'in kendisi CallContext'te saklandığı için, bu ASP.Net'nin HttpContext'i açıkça taşındığı anlamına gelir). Tekrar söyleyelim:
Bunu hep biliyordum ama sürecin o kadar erken olduğunu varsaydım ki umursamadım. Ancak görünüşe göre yanılmışım. ASP.Net uygulamamızda bir sorun yaşıyoruz; kullanıcı bir bağlantıya tıklayır, sonra diğerine tıklar ve uygulamamızda tek bir bağlantımız için null referans istisnası oluşur (ben kullanıyorum Singleton için CallContext değil ThreadStatic ama fark etmediği anlaşılıyor).
ASP'nin tam olarak nasıl yapıldığını biraz araştırdım. Net'in iş parçacılığı çalışıyor ve çelişkili görüşler var, gerçek gibi davranıyor (istekler bir istek içinde thread-agile, istekler ömür boyu bir iş parçacığına sabitleniyor), bu yüzden kendi mesajımı çoğalttım Yavaş sayfa (bir saniye uyku duyumu yapıyor) ve hızlı sayfa olan bir test uygulamasında sorun var. Yavaş sayfa bağlantısına tıklıyorum ve sayfa geri gelmeden önce hızlı sayfa bağlantısına tıklıyorum. Sonuçlar (olanları anlatan log4net bir döküm) beni şaşırttı.
Çıktı gösterdiği şey, ikinci istek için HttpModule boru hattı ve sayfa yapıcısı içindeki BeginRequest olaylarının bir iş parçacığında ateşlendiği, ancak Page_Load başka bir iş parçacığında tetiklendiğidir. İkinci iş parçacığında HttpContext ilkinden taşındı, ancak CallContext veya ThreadStatic'in taşınmadığı (Not: HttpContext kendisi CallContext'te saklandığı için ASP.Net HttpContext'i açıkça aktarıyor). Bunu tekrar açıklayalım:
- İş parçacığı değiştirme, IHttpHandler oluşturulduktan sonra gerçekleşir
- Sayfanın alan başlatıcısı ve yapıcı çalıştırıldıktan sonra
- Global.ASA/IHttpModules tarafından kullanılan herhangi bir BeginRequest, AuthenticateRequest ve AquireSessionState türü olaylardan sonra.
- Sadece HttpContext yeni iş parçacığına taşınır
İş parçacığı anahtarı, IHttpHandler oluşturulduktan sonra gerçekleşir Sayfanın alan başlatıcıları ve yapıcı çalıştırıldıktan sonra Global.ASA / IHttpModule'lerinizin kullandığı herhangi bir BeginRequest, AuthenticateRequest veya AquireSessionState türü olaylardan sonra. Sadece HttpContext yeni iş parçacığına taşınır Bu büyük bir sorun çünkü gördüğüm kadarıyla ASP.Net'daki "ThreadStatic" sınıfının davranışı için tek kalıcı seçeneğin HttpContext kullanmak olduğu anlamına geliyor. Yani iş nesneleriniz için ya if(HttpContext.Current!) kullanmaya devam edersiniz. =null) ve System.Web referansı () ya da statik kalıcılık için bir tür sağlayıcı modeli geliştirmeniz gerekir, bu tek tekli sistemlere erişmeden önce bu modelin kurulması gerekir. Çift mide bulantısı.
Lütfen bunun böyle olmadığını söyleyin.
Ek: Tam günlük:
Bu büyük bir sorun, çünkü gördüğüm kadarıyla ASP.Net' ThreadStatic' benzeri davranış için tek kalıcı seçeneğin HttpContext kullanmak olduğu anlamına geliyor. Yani iş nesneleriniz için ya if(HttpContext.Current!=null) ve System.Web referansıyla () takılı kalırsınız ya da sizin için bir tür sağlayıcı modeli bulmanız gerekir statik dayanıklılık, bu teklilere erişilmeden önce kurulması gerekir. Çift iğrençlik.
Lütfen biri böyle olmadığını söylesin.
Appendix: That log in full: [3748] BILGI 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - BAŞLA /ConcurrentRequestsDemo/SlowPage.aspx [3748] BILGI 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(şimdi)=97, calldata= [3748] BILGI 11:10:05,249 ASP. SlowPage_aspx.. ctor() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748 [3748] BILGI 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748 [3748] BILGI 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - Yavaş sayfa uyuyor....
[2720] BILGI 11:10:05,669 ASP. Global_asax. Application_BeginRequest() - BAŞLA /ConcurrentRequestsDemo/FastPage.aspx [2720] BILGI 11:10:05,679 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(şimdi)=1835, calldata= [2720] BILGI 11:10:05,679 ASP. FastPage_aspx.. ctor() - threadid=2720, threadhash=(cctor)1835, threadhash(now)=1835, calldata=2720, logicalcalldata=2720, threadstatic=2720
[3748] BILGI 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Yavaş sayfa uyanıyor.... [3748] BILGI 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(now)=97, calldata=3748, logicalcalldata=3748 [3748] BILGI 11:10:06,350 ASP. Global_asax. Application_EndRequest() - threadid=3748, threadhash=97, threadhash(şimdi)=97, calldata=3748 [3748] BILGI 11:10:06,350 ASP. Global_asax. Application_EndRequest() - SON /ConcurrentRequestsDemo/SlowPage.aspx
[4748] BILGI 11:10:06,791 ASP. FastPage_aspx. Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(şimdi)=1703, calldata=, logicalcalldata=, threadstatic= [4748] BILGI 11:10:06,791 ASP. Global_asax. Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(şimdi)=1703, calldata= [4748] BILGI 11:10:06,791 ASP. Global_asax. Application_EndRequest() - SON /ConcurrentRequestsDemo/FastPage.aspx Anahtar nokta, FastPage'in Page_Load tetiklendiğinde ne olacağıdır. ThreadID 4748, ancak ctor'un HttpContext sisteminde saklanan ThreadID I 2720'dir. Mantıksal iş parçacıkları için hash kodu 1703, ancak ctor'da sakladığım hash kodu 1835. CallContext'te sakladığım tüm veriler eksik (ILogicalThreadAffinnatif olarak işaretlenmiş veri bile), ama HttpContext hâlâ orada. Tahmin edebileceğiniz gibi, ThreadStatic'im de yok.
Önemli olan, FastPage'in Page_Load ateşlendiğinde ne olacağı. ThreadID 4748, ancak HttpContext'te ctor'da sakladığım threadID 2720. Mantıksal iş parçacığına göre hash kodu 1703, ancak ctor'da sakladığım kod 1835. CallContext'te sakladığım tüm veriler yok (ILogicalThreadAffinative işareti olan da bile), ama HttpContext hâlâ orada. Beklediğiniz gibi, ThreadStatic'im de yok. (Son)
|