Dieser Artikel ist ein Spiegelartikel der maschinellen Übersetzung, bitte klicken Sie hier, um zum Originalartikel zu springen.

Ansehen: 3568|Antwort: 2

[Quelle] ASP.NET der Unterschied zwischen ThreadStatic, CallContext und HttpContext

[Link kopieren]
Veröffentlicht am 30.06.2023 20:34:10 | | |
Original:Der Hyperlink-Login ist sichtbar.



Zusammenfassung:
Selbst wenn du denkst, du weißt, was du tust, ist es nicht sicher, irgendetwas in einem ThreadStatic-Member, CallContext oder Thread Local Store in einer ASP.Net Anwendung zu speichern, wenn der Wert vorher festgelegt wurde. um zu Page_Load (z. B. in IHttpModule oder Seitenkonstruktor), aber während oder nach dem Zugriff.

[Aktualisierung: August 2008 Da weiterhin viele Leute auf diesen Artikel verlinken, halte ich es für notwendig klarzustellen, dass dieses Thread-Swap-Verhalten zu einem sehr spezifischen Zeitpunkt im Lebenszyklus der Seite auftritt und nicht zu dem Moment, in dem es sich danach erscheint.] Meine Formulierung nach dem Zitat von Jeff Newsom ist bedauerlich. Abgesehen davon freue ich mich sehr (und fühle mich geehrt), mehrfache Verweise auf diesen Artikel in Designdiskussionen über den richtigen Umgang mit HttpContext zu sehen. Ich freue mich, dass die Leute es nützlich finden. ]

Es herrscht viel Verwirrung darüber, wie benutzerspezifische Singletons in ASP.Net implementiert werden können – das heißt, globale Daten sind nur für einen Nutzer oder eine Anfrage global. Dies ist keine ungewöhnliche Anforderung: Transaktions-, Sicherheitskontext- oder andere "globale" Daten an einem Ort zu veröffentlichen, anstatt sie in jeden Methodenaufruf zu übertragen, da Loam-Daten eine klarere (und lesbarere) Implementierung ermöglichen. Wenn man jedoch nicht aufpasst, ist dies ein großartiger Ort, um sich selbst in den Fuß (oder Kopf) zu schießen. Ich dachte, ich wüsste, was los war, aber ich wusste es nicht.

Die bevorzugte Option ist SingletonGespeichert in HttpContext.Current.Items, einfach und sicher, aber der betreffende Einzelfall mit seiner Verwendung in der ASP.Net Anwendung in Verbindung bringt. Wenn der Singleton in Ihrem Geschäftsobjekt scheitert, ist das nicht ideal. Selbst wenn du den Property-Zugriff in eine if-Anweisung packst


Summary:
Selbst wenn du denkst, du weißt, was du tust, ist es nicht sicher, irgendetwas in einem ThreadStatic-Mitglied, CallContext oder Thread Local Storage innerhalb einer ASP.Net Anwendung zu speichern, falls es möglich ist dass der Wert vor Page_Load eingerichtet werden könnte (z. B. in IHttpModule oder Page Constructor), aber währenddessen oder danach abgerufen wird.

[Aktualisierung: Aug 2008 Angesichts der relativ großen Anzahl von Leuten, die weiterhin auf diesen Beitrag verlinken, möchte ich klarstellen, dass dieses Thread-Swap-Verhalten an einer sehr spezifischen Stelle auf der Seite auftritt Lebenszyklus und nicht wann immer es sich anfühlt. Meine Formulierung nach dem Zitat von Jef Newson war bedauerlich. Abgesehen davon war ich sehr erfreut (und geschmeichelt) darüber, wie oft ich diesen Beitrag in Designdiskussionen zitiert gesehen habe, wie man angemessen mit HttpContext umgeht. Ich freue mich, dass die Leute es nützlich fanden.]

Es gibt viel Verwirrung darüber, wie man benutzerspezifische Singletons in ASP.Net implementiert – also globale Daten, die nur für einen Nutzer oder eine Anfrage global sind. Dies ist keine ungewöhnliche Anforderung: Transaktionen, Sicherheitskontext oder andere 'globale' Daten an einem Ort zu veröffentlichen, anstatt sie durch jeden Methodenaufruf zu pushen, wie Tramp-Daten es ermöglichen kann Sauberere (und lesbarere) Implementierung. Allerdings ist es ein großartiger Ort, um sich selbst in den Fuß (oder Kopf) zu schießen, wenn man nicht aufpasst. Ich dachte, ich wüsste, was los war, aber ich wusste es nicht.

Die bevorzugte Option, Ihre Singletons in HttpContext.Current.Items zu speichern, ist einfach und sicher, bindet den betreffenden Singleton jedoch an die Nutzung innerhalb einer ASP.Net Anwendung. Wenn der Einzelspieler in deinen Geschäftsobjekten unten ist, ist das nicht ideal. Selbst wenn du den property-access in eine if-Anweisung packst

... dann muss man immer noch System.Web aus dieser Assembly referenzieren, was dazu neigt, mehr 'Webby'-Objekte an der falschen Stelle zu enkorieren.

Die Alternativen sind die Verwendung eines [ThreadStatic] statischen Mitglieds, eines lokalen Thread-Speichers (was im Grunde dasselbe bedeutet) oder CallContext.

Die Probleme mit [ThreadStatic] sind gut dokumentiert, aber zusammengefasst:
Feldinitalizer feuern nur auf den ersten Thread
ThreadStatic-Daten müssen explizit bereinigungen werden (z. B. in EndRequest), denn obwohl der ThreadStatic-Thread erreichbar ist, werden die ThreadStatic-Daten nicht GC-generiert, sodass du möglicherweise Ressourcen ausleckst.
ThreadStatic-Daten sind nur innerhalb einer Anfrage nützlich, weil die nächste Anfrage in einem anderen Thread eingehen und die Daten von jemand anderem erhalten kann.
Scott Hanselman hat recht, dass ThreadStatic nicht gut mit ASP.Net zusammenspielt, erklärt aber nicht vollständig, warum.

Der Speicher in CallContext lindert einige dieser Probleme, da der Kontext am Ende der Anfrage abstirbt und GC irgendwann auftritt (obwohl man Ressourcen weiterhin leaken kann, bis der GC auftritt, falls du lagerst Einweg-Gegenstände). Außerdem wird CallContext gespeichert, also muss das doch in Ordnung sein, oder? Unabhängig davon würde man (wie ich) denken, dass, wenn man am Ende jeder Anfrage hinter sich aufräumt, alles in Ordnung wäre:
"Wenn du die ThreadStatic-Variable zu Beginn der Anfrage initialisierst und das referenzierte Objekt am Ende der Anfrage korrekt handhabst, würde ich riskieren zu behaupten, dass nichts Schlimmes passiert

"Jetzt,Ich könnte mich irren. CLR könnte mitten im Gang aufhören, einen Thread zu hosten, seinen Stack irgendwo serialisieren, ihm einen neuen Stack geben und ihn mit der Ausführung beginnen zu lassen。 Ich bezweifle das zutiefst. Ich denke, es ist denkbar, dass Hyperthreading die Sache auch erschweren würde, aber ich bezweifle es auch. ”

Jeff Newsom


"Wenn du eine ThreadStatic-Variable zu Beginn einer Anfrage initialisierst und das referenzierte Objekt am Ende der Anfrage ordnungsgemäß entsorgst, werde ich behaupten, dass nichts passiert Schlechtes wird passieren. Du bist sogar zwischen Kontexten im selben AppDomain cool

"Nun, ich könnte mich da irren. Der CLR könnte einen verwalteten Thread möglicherweise mitten im Stream stoppen, seinen Stack irgendwo serialisieren, ihm einen neuen Stack geben und ihn mit der Ausführung beginnen lassen. Ich bezweifle es ernsthaft. Ich nehme an, es ist denkbar, dass Hyperthreading die Dinge ebenfalls erschwert, aber ich bezweifle das auch."
Jef Newsom
Update: Das ist irreführend. Ich erkläre später genauer, dass dieser Thread-Tausch nur zwischen BeginRequest und Page_Load stattfinden kann, aber Jefs Referenz erzeugt ein sehr starkes Bild, das ich nicht sofort korrigieren konnte.

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.
Irgendwann entscheide ASP.NET, dass es zu viele I/O-Threads gibt, die andere Anfragen bearbeiten. […] Es akzeptiert nur Anfragen und legt sie innerhalb ASP.NET Laufzeit in einem internen Warteschlangenobjekt. Nach dem Warteschlangen fordert der I/O-Thread einen Worker-Thread an, und der I/O-Thread kehrt dann zu seinem Pool zurück. […] Daher überlassen ASP.NET diesem Mitarbeiter die Anfrage. Es bringt es auf ASP.NET Laufzeit, genau wie der I/O-Thread bei geringer Auslastung.

Irgendwann entscheidet ASP.NET, dass es zu viele I/O-Threads gibt, die andere Anfragen bearbeiten. [...] Es nimmt einfach die Anfrage und stellt sie in diesem internen Warteschlangenobjekt innerhalb der ASP.NET Laufzeit. Nachdem dieser in die Warteschlange gesetzt wurde, fragt der I/O-Thread nach einem Worker-Thread, und der I/O-Thread wird dann in seinen Pool zurückgegeben. [...] Also lässt ASP.NET den Worker-Thread die Anfrage bearbeiten. Es wird in die ASP.NET Laufzeit aufgenommen, genau wie es der I/O-Thread bei geringer Last tun würde.

Ich wusste es immer, aber ich glaube, es ist sehr früh passiert und es war mir egal. Allerdings scheine ich mich zu irren. Wir hatten in ASP.Net App ein Problem, bei dem ein Nutzer nach einem anderen Link auf einen Link geklickt hat, und unsere App hatte eine Null-Referenz-Ausnahme in einem der Singletons (ich habe CallContext statt ThreadStatic für den Singleton verwendet, aber das war irrelevant).

Ich habe recherchiert, wie genau ASP.Net Threads funktionieren, und habe widersprüchliche Meinungen bekommen, die als Fakten getarnt sind (Anfragen sind in Anfragen thread-agil, während Anfragen während ihrer Lebensdauer in Threads verankert sind), also habe ich mein Problem in einer Testanwendung mit einer langsamen Seite (einen Moment im Schlaf) und einer schnellen Seite kopiert. Ich klicke auf den Link zur langsamen Seite und bevor die Seite zurückkehrt, klicke ich auf den Link zur schnellen Seite. Das Ergebnis (log4net-Dump dessen, was passiert) hat mich umgehauen.

Die Ausgabe zeigt, dass bei der zweiten Anfrage das BeginRequest-Ereignis und der Seitenkonstruktor in der HttpModule-Pipeline auf einem Thread ausgelöst werden, in einem anderen jedoch page_Load. Der zweite Thread hat bereits den HttpContext vom ersten Thread migriert, aber nicht den CallContext oder den ThreadStatic (Hinweis: Da der HttpContext selbst im CallContext gespeichert ist, bedeutet das, dass ASP.Net explizit den HttpContext migriert). Sagen wir es nochmal:


Ich wusste das schon immer, aber ich nahm an, dass es früh genug im Prozess passiert ist, sodass es mir egal war. Es scheint jedoch, dass ich mich geirrt habe. Wir haben ein Problem in unserer ASP.Net-App, bei dem der Nutzer direkt nach dem Anklicken eines anderen auf einen Link klickt, und unsere App explodiert mit einer Null-Referenz-Ausnahme für einen unserer Singletons (ich benutze CallContext, nicht ThreadStatic für den Singleton, aber es stellt sich heraus, dass das keine Rolle spielt).

Ich habe ein wenig recherchiert, wie genau ASP funktioniert. Nets Threading funktioniert und es gab widersprüchliche Meinungen, die sich als Fakt tarnen (Anfragen sind innerhalb einer Anfrage thread-agil, während Anfragen für ihre Lebensdauer an einen Thread gebunden sind), also habe ich meine Problem in einer Testanwendung mit einer langsamen Seite (schläft eine Sekunde) und einer schnellen Seite. Ich klicke auf den Link zur langsamen Seite und bevor die Seite zurückkommt, klicke ich auf den Link zur schnellen Seite. Die Ergebnisse (ein log4net-Dump dessen, was passiert) haben mich überrascht.

Was die Ausgabe zeigt, ist, dass – für die zweite Anfrage – die BeginRequest-Ereignisse in der HttpModule-Pipeline und der Seitenkonstruktor auf einem Thread ausgelöst werden, während der Page_Load auf einem anderen ausgelöst wird. Der zweite Thread hat den HttpContext vom ersten migriert, aber nicht den von CallContext oder ThreadStatic (NB: Da HttpContext selbst in CallContext gespeichert ist, bedeutet das, ASP.Net ist Migration des HttpContext explizit hinüber). Lassen Sie uns das nochmal ausbuchstabieren:

  • Das Thread-Switching erfolgt nach der Erstellung des IHttpHandler
  • Nachdem der Seitenfeldinitialisierer und Konstruktor ausgeführt werden
  • Nach allen BeginRequest-, AuthenticateRequest- und AquireSessionState-Typen, die von Global.ASA/IHttpModules verwendet werden.
  • Nur der HttpContext wird in den neuen Thread migriert



Der Thread-Wechsel erfolgt, nachdem der IHttpHandler erstellt wurde
Nachdem die Feldinitialisierer und der Konstruktor der Seite ausgeführt werden
Nach allen BeginRequest-, AuthenticateRequest- und AquireSessionState-Typen, die Ihre Global.ASA / IHttpModules verwenden.
Nur der HttpContext migriert in den neuen Thread

Das ist ein großer Ärgernis, denn soweit ich gesehen habe, bedeutet das, dass die einzige Persistenzoption für das Verhalten der "ThreadStatic"-Klasse in ASP.Net darin besteht, HttpContext zu verwenden. Für deine Geschäftsobjekte benutzt du entweder weiterhin if(HttpContext.Current!). =null) und System.Web-Referenz (igitt), oder man muss ein Provider-Modell für statische Persistenz entwickeln, das eingerichtet werden muss, bevor man auf diese Singletons zugreifen kann. Doppelte Übelkeit.

Bitte sagen Sie, dass das nicht der Fall ist.

Anhang: Vollständiges Logbuch:


Das ist wirklich nervig, denn soweit ich sehe, bedeutet das, dass die einzige Persistenzoption für 'ThreadStatic'-ähnliches Verhalten in ASP.Net darin besteht, HttpContext zu verwenden. Für deine Geschäftsobjekte bist du entweder auf das if(HttpContext.Current!=null) und die System.Web-Referenz (igitt) angewiesen oder du musst dir ein Provider-Modell für deinen Provider ausdenken statische Persistenz, die eingerichtet werden muss, bevor einer dieser Singletons zugänglich ist. Doppelte Igitt.

Bitte, jemand sagt, dass es nicht so ist.

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(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() - Langsamer Seitenschlaf....

[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(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() - Langsame Seite wacht auf....
[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() - ENDE /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() - ENDE /ConcurrentRequestsDemo/FastPage.aspx
Der Schlüssel ist, was passiert, wenn der Page_Load von FastPage ausgelöst wird. Die ThreadID ist 4748, aber die ThreadID, die ich im HttpContext von ctor gespeichert habe, ist 2720. Der Hashcode für logische Threads ist 1703, aber der Hashcode, den ich in ctor speichere, ist 1835. Alle Daten, die ich im CallContext gespeichert habe, fehlen (sogar die Daten, die als ILogicalThreadAffinnative gekennzeichnet sind), aber der HttpContext ist noch vorhanden. Wie zu erwarten, ist auch mein ThreadStatic weg.

Der entscheidende Punkt ist, was passiert, wenn FastPages Page_Load ausgelöst wird. Die ThreadID ist 4748, aber die ThreadID, die ich im HttpContext im CTOR gespeichert habe, ist 2720. Der Hashcode für den logischen Thread ist 1703, aber der, den ich im ctor gespeichert habe, ist 1835. Alle Daten, die ich im CallContext gespeichert habe, sind verschwunden (sogar die markierten ILogicalThreadAffinative), aber HttpContext ist noch vorhanden. Wie zu erwarten, ist auch mein ThreadStatic weg.
(Ende)




Vorhergehend:.NET/C#-Sammlung Any() oder Count(), was schneller ist
Nächster:Wie Lazy in C# Threads sicher hält
 Vermieter| Veröffentlicht am 30.06.2023 20:35:23 |
 Vermieter| Veröffentlicht am 02.07.2023 09:59:06 |
CallContext

Namensraum: System.Runtime.Remoting.Messaging
Typvoll qualifizierter Name: System.Runtime.Remoting.Messaging.CallContext
Offizielle Einführung:Der Hyperlink-Login ist sichtbar.

Zweck: Eine Menge von Attributen bereitzustellen, die zusammen mit dem Ausführungspfad übergeben werden, einfach gesagt: Die Möglichkeit zu ermöglichen, Daten im Ausführungspfad von gethreadtem (Multi-/Single-Threaded) Code weiterzugeben.
MethodeBeschreibungOb es in einer Multithread-Umgebung verwendet werden kann
SetDataSpeichere ein bestimmtes Objekt und verknüpfe es mit einem bestimmten Namen.nicht
GetDataHolen Sie das Objekt mit dem angegebenen Namen aus System.Runtime.Remoting.Messaging.CallContext abnicht
LogicalSetDataSpeichere ein bestimmtes Objekt im Kontext eines logischen Aufrufs und ordne es einem bestimmten Namen zu.sein
LogicalGetDataRufen Sie Objekte mit angegebenen Namen aus dem logischen Aufrufkontext ab.sein
FreeNamedDataSlotLeere die Datenschlitze mit dem angegebenen Namen.sein
HostContextErhalte oder setze den Host-Kontext, der mit dem aktuellen Thread verknüpft ist. In der Webumgebung ist es gleich System.Web.HttpContext.Currentnicht


Verzichtserklärung:
Alle von Code Farmer Network veröffentlichten Software, Programmiermaterialien oder Artikel dienen ausschließlich Lern- und Forschungszwecken; Die oben genannten Inhalte dürfen nicht für kommerzielle oder illegale Zwecke verwendet werden, andernfalls tragen die Nutzer alle Konsequenzen. Die Informationen auf dieser Seite stammen aus dem Internet, und Urheberrechtsstreitigkeiten haben nichts mit dieser Seite zu tun. Sie müssen die oben genannten Inhalte innerhalb von 24 Stunden nach dem Download vollständig von Ihrem Computer löschen. Wenn Ihnen das Programm gefällt, unterstützen Sie bitte echte Software, kaufen Sie die Registrierung und erhalten Sie bessere echte Dienstleistungen. Falls es eine Verletzung gibt, kontaktieren Sie uns bitte per E-Mail.

Mail To:help@itsvse.com