Ten artykuł jest lustrzanym artykułem tłumaczenia maszynowego, kliknij tutaj, aby przejść do oryginalnego artykułu.

Widok: 3568|Odpowiedź: 2

[Źródło] ASP.NET różnica między ThreadStatic, CallContext i HttpContext

[Skopiuj link]
Opublikowano 30.06.2023 20:34:10 | | |
Oryginał:Logowanie do linku jest widoczne.



Streszczenie:
Nawet jeśli myślisz, że wiesz, co robisz, nie jest bezpiecznie przechowywać czegokolwiek w sklepie ThreadStatic member, CallContext lub Thread Local w aplikacji ASP.Net, jeśli wartość została ustawiona wcześniej. do Page_Load (np. w module IHttpModule lub konstruktorze strony), ale w trakcie lub po dostępie.

[Aktualizacja: sierpień 2008 Biorąc pod uwagę, że sporo osób nadal linkuje do tego artykułu, uważam za konieczne wyjaśnienie, że takie zachowanie związane z zamianą wątków występuje w bardzo konkretnym momencie cyklu życia strony, a nie wtedy, gdy tak się czuje.] Moje sformułowanie po cytowaniu Jeffa Newsoma jest niefortunne. Poza tym bardzo się cieszę (i jestem zaszczycony), że w dyskusjach projektowych dotyczących właściwego obsługiwania HttpContext wielokrotnie pojawiają się odniesienia do tego artykułu. Cieszę się, że ludzie uważają to za przydatne. ]

Jest wiele zamieszania co do implementacji singletonów specyficznych dla użytkownika w ASP.Net – to znaczy, globalne dane są globalne tylko dla jednego użytkownika lub żądania. To nie jest rzadki wymóg: publikować dane transakcyjne, kontekstowe lub inne "globalne" w jednym miejscu, zamiast wrzucać je do każdego wywołania metody, ponieważ dane loam umożliwiają jaśniejsze (i bardziej czytelne) wdrożenie. Jednak jeśli nie będziesz ostrożny, to świetne miejsce, by strzelić sobie w stopę (lub głowę). Myślałem, że wiem, co się dzieje, ale nie wiedziałem.

Preferowaną opcją będzie singletonPrzechowywane w HttpContext.Current.Items, proste i bezpieczne, ale odnosi się do pojedynczego z jego zastosowaniem w ASP.Net zastosowaniu. Jeśli singleton zawiodł w Twoim biznesowym dokumencie, to nie jest to idealne. Nawet jeśli oplatasz dostęp do właściwości w instrukcji if


Summary:
Nawet jeśli myślisz, że wiesz, co robisz, nie jest bezpieczne przechowywać czegokolwiek w członku ThreadStatic, CallContext lub Thread Local Storage w ASP.Net aplikacji, jeśli istnieje taka możliwość że wartość może być ustawiona przed Page_Load (np. w IHttpModule lub page constructor), ale dostępna w trakcie lub po niej.

[Aktualizacja: sierpień 2008 Biorąc pod uwagę dość dużą liczbę osób nadal linkujących do tego posta, czuję potrzebę wyjaśnienia, że to zachowanie związane z zamianą wątków występuje w bardzo konkretnym miejscu na stronie Cykl życia i nie wtedy, kiedy tylko się czuje. Moje sformułowania po cytacie Jefa Newsona były niefortunne. Poza tym, byłem ogromnie satysfakcjonowany (i pochlebiony) z tego, ile razy widziałem ten post cytowany w dyskusjach projektowych dotyczących właściwego korzystania z HttpContext. Cieszę się, że ludzie uznali to za przydatne.]

Jest dużo zamieszania co do implementacji singletonów specyficznych dla użytkownika w ASP.Net – czyli globalnych danych globalnych tylko dla jednego użytkownika lub żądania. To nie jest rzadki wymóg: publikowanie Transakcji, kontekstu bezpieczeństwa lub innych "globalnych" danych w jednym miejscu, zamiast przesyłania ich przez każde wywołanie metody, jak to mogą być dane trampowe Czystsza (i bardziej przystępna) realizacja. Jednak to świetne miejsce, by strzelić sobie w stopę (lub głowę), jeśli nie jesteś ostrożny. Myślałem, że wiem, co się dzieje, ale nie wiedziałem.

Preferowaną opcją, czyli przechowywanie singletonów w HttpContext.Current.Items, jest prosta i bezpieczna, ale wiąże się z użyciem singletonów w aplikacji ASP.Net. Jeśli singleton jest nieobecny w obiektach biznesowych, to nie jest to idealne. Nawet jeśli owiniesz dostęp do właściwości w instrukcji if

... wtedy nadal trzeba odwoływać się do System.Web z tego asemblera, który zwykle integruje więcej "webowych" obiektów w niewłaściwym miejscu.

Alternatywami są użycie statycznego członka [ThreadStatic], lokalnej pamięci Thread (co w zasadzie daje to samo) lub CallContext.

Problemy z [ThreadStatic] są dobrze udokumentowane, ale podsumowując:
Initalizery polowe uruchamiają się dopiero w pierwszym wątku
Dane ThreadStatic wymagają wyraźnego czyszczenia (np. w EndRequest), ponieważ choć wątek jest dostępny, dane ThreadStatic nie będą zabezpieczone przez GC, więc możesz wyciekać zasoby.
Dane ThreadStatic są przydatne tylko w ramach żądania, ponieważ kolejne żądanie może pojawić się w innym wątku i pobrać czyjeś dane.
Scott Hanselman dobrze to ujął, że ThreadStatic nie współpracuje z ASP.Net, ale nie wyjaśnia do końca dlaczego.

Przechowywanie w CallContext łagodzi niektóre z tych problemów, ponieważ kontekst zanika na końcu żądania, a GC w końcu nastąpi (choć nadal można wyciekać zasoby do czasu GC, jeśli przechowujesz jednorazowe przedmioty). Dodatkowo CallContext to sposób przechowywania HttpContext, więc musi być w porządku, prawda? Niezależnie od tego, można by pomyśleć (tak jak ja), że jeśli po każdym prośbie posprzątasz po sobie, wszystko będzie w porządku:
"Jeśli zainicjujesz zmienną ThreadStatic na początku żądania i poprawnie obsłużysz obiekt na końcu żądania, ryzykowałbym twierdzenie, że nic złego się nie stanie

"A teraz,Mogę się mylić. CLR może przestać hostować wątek w połowie, serializować jego stos gdzieś, dać mu nowy stos i pozwolić mu zacząć się uruchamiać。 Bardzo w to wątpię. Wydaje mi się, że hiperwątkowanie też utrudniłoby sprawę, ale też w to wątpię. ”

Jeff Newsom


"Jeśli zainicjujesz zmienną ThreadStatic na początku żądania i właściwie pozbędziesz się obiektu na końcu żądania, to ryzykuję, że nic nie ma Złe się zdarzy. Jesteś nawet fajny między kontekstami w tej samej AppDomain

"Mogę się mylić. CLR mógłby potencjalnie zatrzymać zarządzany wątek w trakcie strumienia, rozdzielić jego stos, nadać mu nowy stos i pozwolić rozpocząć wykonywanie. Szczerze w to wątpię. Przypuszczam, że możliwe jest, iż hiperwątkowanie też utrudnia sprawę, ale też w to wątpię."
Jef Newsom
Aktualizacja: To wprowadza w błąd. Później wyjaśnię dokładniej, że ta zamiana wątków może nastąpić tylko między BeginRequest a Page_Load, ale odniesienie Jefa tworzy bardzo mocny obraz, którego nie poprawiłem od razu.

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.
W pewnym momencie ASP.NET uznać, że jest zbyt wiele wątków I/O przetwarzających inne żądania. […] Akceptuje tylko żądania i kolejkuje je w wewnętrznym obiekcie kolejki w ASP.NET czasie działania. Następnie, po kolejce wątku I/O, zażąda wątku roboczego, a następnie wątek I/O wraca do swojej puli. […] Dlatego ASP.NET pozwolić temu pracownikowi zająć się tym żądaniem. To przeprowadzi go do ASP.NET runtime, tak jak wątek I/O przy niskim obciążeniu.

W pewnym momencie ASP.NET uznał, że jest zbyt wiele wątków I/O przetwarzających inne żądania. [...] Po prostu przyjmuje żądanie i ustawia je w kolejce wewnętrznej w tym obiekcie kolejki w ASP.NET runtime. Następnie, po tym założeniu wątku I/O poprosi o wątek roboczy, a następnie wątek I/O zostanie zwrócony do swojej puli. [...] Więc ASP.NET ten wątek roboczy przetwarza żądanie. To przeniesie go do ASP.NET runtime, tak jak wątek I/O przy niskim obciążeniu.

Zawsze o tym wiedziałem, ale myślę, że stało się to bardzo wcześnie i nie obchodziło mnie to. Jednak chyba się mylę. Mieliśmy problem ASP.Net w aplikacji, gdzie użytkownik kliknął link po kliknięciu w inny link, a nasza aplikacja miała wyjątek null reference w jednym z singletonów (użyłem CallContext zamiast ThreadStatic dla singleton, ale okazało się to nieistotne).

Zrobiłem trochę researchu, jak dokładnie działają ASP.Net wątki, i otrzymałem sprzeczne opinie ukryte pod faktami (żądania są zwinne w żądaniach, podczas gdy są zakotwiczone na wątkach przez cały okres życia), więc skopiowałem mój problem w aplikacji testowej z wolną stroną (chwilę uśpienia) i szybką stroną. Klikam link do wolnej strony, a zanim strona wróci, klikam w link do szybkiej strony. Efekt (log4net dump tego, co się dzieje) zszokował mnie.

Wynik pokazuje, że dla drugiego żądania zdarzenie BeginRequest i konstruktor strony w potoku HttpModule uruchamiają się na jednym wątku, a page_Load na innym. Drugi wątek już przeniósł HttpContext z pierwszego wątku, ale nie CallContext ani ThreadStatic (uwaga: ponieważ sam HttpContext jest przechowywany w CallContext, oznacza to, że ASP.Net jawnie migruje HttpContext). Powtórzmy to jeszcze raz:


Zawsze o tym wiedziałem, ale zakładałem, że stało się to na tyle wcześnie, że mnie to nie obchodziło. Wygląda jednak na to, że się myliłem. Mamy problem w naszej aplikacji ASP.Net, gdzie użytkownik klika jeden link zaraz po kliknięciu innego, a nasza aplikacja wybucha z wyjątkiem null reference dla jednego z naszych singletonów (używam CallContext, a nie ThreadStatic dla singletona, ale okazuje się, że to nie ma znaczenia).

Trochę poszukałem na temat tego, jak dokładnie ASP. Wątki Netu działają i pojawiły się sprzeczne opinie – udające fakty (żądania są zwinne w ramach żądania, a żądania przypisane do wątku przez cały okres życia), więc powtórzyłem mój Problem w aplikacji testowej z wolną stroną (uśpieniem na sekundę) i szybką stroną. Klikam link do wolniejszej strony i zanim strona wróci, klikam link do szybkiej strony. Wyniki (log4net wyrzut tego, co się dzieje) mnie zaskoczyły.

Wyjście pokazuje, że dla drugiego żądania zdarzenia BeginRequest w potoku HttpModule i konstruktor strony uruchamiają się na jednym wątku, a Page_Load na innym. Drugi wątek miał HttpContext przeniesiony z pierwszego, ale nie CallContext ani ThreadStatic (uwaga: ponieważ HttpContext jest przechowywany w CallContext, oznacza to, że ASP.Net jest wyraźnie migrując HttpContext na drugą stronę). Wyjaśnijmy to jeszcze raz:

  • Przełączanie wątków następuje po utworzeniu IHttpHandlera
  • Po uruchomieniu inicjalizatora pola i konstruktora strony
  • Po każdym zdarzeniu typu BeginRequest, AuthenticateRequest, AquireSessionState, które są wykorzystywane przez Global.ASA/IHttpModules.
  • Tylko HttpContext jest migrowany do nowego wątku



Zmiana wątku następuje po utworzeniu IHttpHandlera
Po uruchomieniu inicjalizatorów pól i konstruktora strony
Po każdym zdarzeniu typu BeginRequest, AuthenticateRequest, AquireSessionState, które używają Twoje Global.ASA / IHttpModule.
Tylko HttpContext migruje do nowego wątku

To spora udręka, bo z tego, co widziałem, oznacza to, że jedyną opcją zachowania zachowania klasy "ThreadStatic" w ASP.Net jest użycie HttpContext. Więc dla obiektów biznesowych albo dalej używasz if(HttpContext.Current!). =null) i referencji System.Web (fuj), albo trzeba wymyślić jakiś model dostawcy statycznej trwałości, który trzeba skonfigurować przed uzyskaniem dostępu do tych singletonów. Podwójne nudności.

Proszę powiedzieć, że tak nie jest.

Aneks: Pełny dziennik:


To spora udręka, bo z tego co widzę, oznacza to, że jedyną opcją persystencji zachowania w stylu 'ThreadStatic' w ASP.Net jest użycie HttpContext. Więc dla obiektów biznesowych albo zostajesz skazany na if(HttpContext.Current!=null) i referencję System.Web (ble), albo musisz wymyślić jakiś model dostawcy Statyczna trwałość, którą trzeba skonfigurować przed momentem, gdy którykolwiek z tych singletonów zostanie użyty. Podwójnie fuj.

Proszę, niech ktoś to nie powie.

Appendix: That log in full:
[3748] INFORMACJE 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - BEGIN /ConcurrentRequestsDemo/SlowPage.aspx
[3748] INFORMACJE 11:10:05,239 ASP. Global_asax. Application_BeginRequest() - threadid=, threadhash=, threadhash(teraz)=97, calldata=
[3748] INFORMACJE 11:10:05,249 ASP. SlowPage_aspx.. ctor() - threadid=3748, threadhash=(cctor)97, threadhash(teraz)=97, calldata=3748, logicalcalldata=3748
[3748] INFORMACJE 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(teraz)=97, calldata=3748, logicalcalldata=3748
[3748] INFORMACJE 11:10:05,349 ASP. SlowPage_aspx. Page_Load() - Powolne śpienie strony....

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

[3748] INFORMACJE 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - Powolna strona się budzi....
[3748] INFORMACJE 11:10:06,350 ASP. SlowPage_aspx. Page_Load() - threadid=3748, threadhash=(cctor)97, threadhash(teraz)=97, calldata=3748, logicalcalldata=3748
[3748] INFORMACJE 11:10:06,350 ASP. Global_asax. Application_EndRequest() - threadid=3748, threadhash=97, threadhash(teraz)=97, calldata=3748
[3748] INFORMACJE 11:10:06,350 ASP. Global_asax. Application_EndRequest() - KONIEC /ConcurrentRequestsDemo/SlowPage.aspx

[4748] INFORMACJE 11:10:06,791 ASP. FastPage_aspx. Page_Load() - threadid=2720, threadhash=(cctor)1835, threadhash(teraz)=1703, calldata=, logicalcalldata=, threadstatic=
[4748] INFORMACJE 11:10:06,791 ASP. Global_asax. Application_EndRequest() - threadid=2720, threadhash=1835, threadhash(teraz)=1703, calldata=
[4748] INFORMACJE 11:10:06,791 ASP. Global_asax. Application_EndRequest() - KONIEC /ConcurrentRequestsDemo/FastPage.aspx
Kluczowe jest to, co się dzieje, gdy Page_Load FastPage zostaje wyzwalany. ThreadID to 4748, ale ThreadID, który zapisałem w HttpContext ctor, to 2720. Kod skrótu dla wątków logicznych to 1703, ale kod skrótu, który przechowuję w ctor, to 1835. Wszystkie dane, które przechowywałem w CallContext, są zniknięte (nawet dane oznaczone jako ILogicalThreadAffinnative), ale HttpContext nadal tam jest. Jak można się spodziewać, mój ThreadStatic też zniknął.

Kluczowe jest, co się dzieje, gdy Page_Load FastPage się uruchamia. ThreadID to 4748, ale threadID, które zapisałem w HttpContext w ctorze, to 2720. Kod skrótu wątku logicznego to 1703, ale ten, który zapisałem w ktorze, to 1835. Wszystkie dane, które przechowywałem w CallContext, zniknęły (nawet te oznaczone jako ILogicalThreadAffinative), ale HttpContext nadal tam jest. Jak można się spodziewać, mój ThreadStatic też zniknął.
(Koniec)




Poprzedni:.NET/C# kolekcja Any() lub Count(), która jest szybsza
Następny:Jak Lazy w C# chroni wątki
 Ziemianin| Opublikowano 30.06.2023 20:35:23 |
 Ziemianin| Opublikowano 02.07.2023 09:59:06 |
CallContext

Przestrzeń nazw: System.Runtime.Relading.Messaging
Nazwa typu w pełni kwalifikowana: System.Runtime.Relading.Messaging.CallContext
Oficjalne wprowadzenie:Logowanie do linku jest widoczne.

Cel: Zapewnienie zestawu atrybutów przekazywanych wraz ze ścieżką kodu wykonawczego, mówiąc prosto: umożliwienie przekazywania danych na ścieżce wykonywania kodu wątkowego (wielowątkowego/jednowątkowego).
metodaopisCzy można go używać w środowisku wielowątkowym
SetDataZapisz dany obiekt i przypisz mu określoną nazwę.nie
GetDataPobierz obiekt o podanej nazwie z System.Runtime.Remoting.Messaging.CallContextnie
LogicalSetDataPrzechowywanie danego obiektu w kontekście wywołania logicznego i powiązanie go z określoną nazwą.być
LogicalGetDataPobieranie obiektów o określonych nazwach z kontekstu wywołań logicznych.być
FreeNamedDataSlotOpróżni sloty danych z podaną nazwą.być
HostContextPobierz lub ustaw kontekst hosta powiązany z bieżącym wątkiem. W środowisku webowym jest równy System.Web.HttpContext.Currentnie


Zrzeczenie się:
Całe oprogramowanie, materiały programistyczne lub artykuły publikowane przez Code Farmer Network służą wyłącznie celom edukacyjnym i badawczym; Powyższe treści nie mogą być wykorzystywane do celów komercyjnych ani nielegalnych, w przeciwnym razie użytkownicy ponoszą wszelkie konsekwencje. Informacje na tej stronie pochodzą z Internetu, a spory dotyczące praw autorskich nie mają z nią nic wspólnego. Musisz całkowicie usunąć powyższą zawartość z komputera w ciągu 24 godzin od pobrania. Jeśli spodoba Ci się program, wspieraj oryginalne oprogramowanie, kup rejestrację i korzystaj z lepszych, autentycznych usług. W przypadku naruszenia praw prosimy o kontakt mailowy.

Mail To:help@itsvse.com