Erhalte SynchronizationContext von einem gegebenen Thread

7

Ich finde nicht, wie man SynchronizationContext eines gegebenen Thread :

bekommt %Vor%

Warum sollte ich das brauchen?

Weil ich den UIThread von verschiedenen Stellen aus über die Front-End-Anwendung posten muss. Also habe ich eine statische Eigenschaft in einer Klasse namens UIConfiguration definiert. Ich habe diese Eigenschaft in der Methode Program.Main festgelegt:

%Vor%

In diesem Moment kann ich sicher sein, dass ich den richtigen Thread habe, aber ich kann keine statische Eigenschaft wie

setzen %Vor%

, weil die WinForms-Implementierung dieser Klasse noch nicht installiert wurde. Da jeder Thread seinen eigenen SynchronizationContext hat, muss es möglich sein, ihn von einem gegebenen Thread -Objekt abzurufen, oder liege ich völlig falsch?

    
chiccodoro 05.11.2010, 15:41
quelle

5 Antworten

12

Dies ist nicht möglich. Das Problem ist, dass ein SynchronizationContext und ein Thread wirklich zwei völlig getrennte Konzepte sind.

Es stimmt zwar, dass Windows Forms und WPF beide einen SynchronizationContext für den Haupt-Thread einrichten, die meisten anderen Threads nicht. Zum Beispiel enthält keiner der Threads im ThreadPool seinen eigenen SynchronizationContext (außer natürlich, Sie installieren Ihren eigenen).

Es ist auch möglich, dass ein SynchronizationContext völlig unabhängig von Threads und Threading ist. Ein Synchronisierungskontext kann einfach eingerichtet werden, der mit einem externen Dienst oder einem gesamten Thread-Pool usw. synchronisiert wird.

In Ihrem Fall würde ich empfehlen, Ihre UIConfiguration.SynchronizationContext auf das Loaded-Ereignis des ersten Hauptformulars zu setzen. Der Kontext wird garantiert an diesem Punkt gestartet und kann nicht verwendet werden, bis die Nachrichtenpumpe auf jeden Fall gestartet wurde.

    
Reed Copsey 05.11.2010, 15:59
quelle
7
Ich weiß, dass dies eine alte Frage ist, und entschuldige mich für den Nekromuniker, aber ich habe gerade eine Lösung für dieses Problem gefunden, von der ich dachte, dass sie für diejenigen von uns nützlich sein könnte, die das gegoogelt haben (und keine Kontrolle benötigt) Instanz).

Grundsätzlich können Sie eine Instanz eines WindowsFormsSynchronizationContext erstellen und den Kontext in Ihrer Main -Funktion wie folgt manuell festlegen:

%Vor%

Ich habe dies in meiner Anwendung getan, und es funktioniert einwandfrei ohne Probleme. Ich sollte jedoch darauf hinweisen, dass mein Main mit STAThread gekennzeichnet ist, also bin ich mir nicht sicher, ob das noch funktionieren wird (oder wenn es sogar notwendig ist), wenn Ihr Main stattdessen mit MTAThread markiert ist.

EDIT: Ich habe vergessen, es zu erwähnen, aber _UISyncContext ist bereits auf der Modulebene in der Klasse Program in meiner Anwendung definiert.

    
Javert93 17.04.2011 12:11
quelle
4

Ich fand als die knappste und hilfreich für mich die folgenden Passagen aus dem Buch von Alex Davies "Async in C # 5.0 O'Reilly Publ 2012..", P.48-49:

  • " SynchronizationContext eine Klasse von dem FRAMEWORK vorgesehen ist, die die Fähigkeit Code auszuführen hat in eine bestimmte Art von Thread .
    Es gibt verschiedene Synchronisationskontexte, die von .NET verwendet werden. Die wichtigsten davon sind die Kontexte für Benutzeroberflächen, die von WinForms und WPF verwendet werden. "

  • "Instanzen von SynchronizationContext selbst tun nichts sehr nützliches, daher sind alle tatsächlichen Instanzen davon Unterklassen.

    Es hat auch statische Mitglieder, mit denen Sie die aktuelle SynchronizationContext lesen und steuern können.

    Das aktuelle SynchronizationContext ist eine Eigenschaft des aktuellen Threads.

    Die Idee ist, dass Sie an jedem Punkt, an dem Sie in einem speziellen Thread laufen, die aktuelle SynchronizationContext erhalten und speichern können. Später können Sie damit Code für den speziellen Thread ausführen, den Sie gestartet haben. All dies sollte möglich sein ohne genau wissen zu müssen, welchen Thread Sie gestartet haben, solange Sie den SynchronizationContext verwenden können, können Sie darauf zurückkommen .

    Die wichtige Methode von SynchronizationContext ist Post , wodurch ein Delegat im richtigen Kontext ausgeführt werden kann .

  • " Einige SynchronizationContexts einen einzigen Thread, wie die UI-Thread kapseln
    Einige encapsulate eine bestimmte Art von Faden - zum Beispiel der Thread-Pool - aber jedes dieser Themen können wählen den Delegaten, um es zu . Einige ändern nicht wirklich den Thread, auf dem der Code ausgeführt wird, sondern werden nur für die Überwachung verwendet, wie der ASP.NET-Synchronisationskontext "

quelle
2

Ich glaube nicht, dass jeder Thread eine eigene SynchronizationContext hat - es hat nur eine Thread-lokale SynchronizationContext .

Warum setzen Sie nicht einfach UIConfiguration.UIThread im Loaded -Ereignis Ihres Formulars oder etwas ähnliches?

    
Jon Skeet 05.11.2010 15:48
quelle
1

Komplette und funktionierende Erweiterungsmethoden zum Abrufen von SynchronizationContext von Thread oder ExecutionContext (oder null , falls keine vorhanden sind), oder von DispatcherSynchronizationContext von Dispatcher . Getestet auf .NET 4.6.2 .

%Vor%

Alle oben genannten Funktionen rufen den unten angegebenen __get Code auf, was eine Erklärung erfordert.

Beachten Sie, dass __get ein statisches Feld ist, das mit einem verwerfbaren Lambda-Block vorinitialisiert ist. Dadurch können wir den ersten Aufrufer nur sauber abfangen, um die einmalige Initialisierung auszuführen, die einen kleinen und permanenten Ersatzdelegaten bereitstellt, der viel schneller und reflexionsfrei ist .

Der letzte Schritt für die unerschrockene Initialisierungsbemühung ist es, die Ersetzung in "__get" zu tauschen, was gleichzeitig und tragisch bedeutet, dass der Code sich selbst verwirft, keine Spur hinterlässt und alle nachfolgenden Aufrufer direkt in die DynamicMethod richtig gehen, ohne sogar a Hinweis auf Bypass-Logik.

%Vor%

Der süße Teil ist das Ende, wo - um tatsächlich den Wert für den vorweggenommenen ersten Aufrufer zu holen - die Funktion sich angeblich mit einem eigenen Argument aufruft, aber Rekursen vermeidet, indem sie sich unmittelbar vorher ersetzt.

Es gibt keinen besonderen Grund, diese ungewöhnliche Technik bei dem speziellen Problem von SynchronizationContext , das auf dieser Seite diskutiert wird, zu demonstrieren. Wenn man das _syncContext -Feld aus einem ExecutionContext herausholt, kann man leicht und trivial mit der traditionellen Reflektion (plus einem Frostschutz der Erweiterungsmethode) umgehen. Aber ich dachte, ich würde diesen Ansatz, den ich persönlich schon seit einiger Zeit nutze, mit anderen teilen, weil er leicht angepasst werden kann und für solche Fälle gleichermaßen anwendbar ist.

Dies ist besonders dann sinnvoll, wenn beim Zugriff auf das nicht öffentliche Feld eine extreme Leistung erforderlich ist. Ich denke, dass ich das ursprünglich in einem QPC-basierten Frequenzzähler verwendet habe, wo das Feld in einer engen Schleife gelesen wurde, die alle 20 oder 25 Nanosekunden wiederholt wurde, was mit herkömmlicher Reflexion nicht wirklich möglich wäre.

Damit ist die Hauptantwort abgeschlossen, aber unten habe ich einige interessante Punkte hinzugefügt, die weniger relevant für die Frage des Fragestellers sind, als für die eben demonstrierte Technik.

Runtime-Aufrufer

Aus Gründen der Klarheit habe ich die Schritte "Installationstausch" und "erste Verwendung" in zwei separate Zeilen im oben gezeigten Code getrennt, im Gegensatz zu dem, was ich in meinem eigenen Code habe (die folgende Version vermeidet auch einen Hauptspeicherabruf) gegenüber der vorherigen, möglicherweise impliziten Thread-Sicherheit, siehe detaillierte Diskussion unten):

%Vor%

Mit anderen Worten, alle Aufrufer, einschließlich der ersten , rufen den Wert auf die gleiche Weise ab, und dazu wird niemals ein Reflexionscode verwendet. Es schreibt nur den Ersatz Getter . Mit freundlicher Genehmigung von il-visualizer können wir den Rumpf dieses DynamicMethod im Debugger zur Laufzeit sehen:

Lock-free Thread-Sicherheit

Ich sollte beachten, dass das Swapping im Funktionskörper eine vollständig Thread-sichere Operation ist, wenn man das .NET Speichermodell und die Lock-Free-Philosophie. Letzteres begünstigt Forward-Progress-Garantien auf Kosten der doppelten oder redundanten Arbeit. Multi-Way-Racing zu initialisieren ist auf einer völlig soliden theoretischen Basis korrekt erlaubt:

  • Der Startpunkt der Rennstrecke (Initialisierungscode) ist global vorkonfiguriert und geschützt (durch den .NET-Lader), so dass (mehrere) Rennfahrer (falls vorhanden) den gleichen Initialisierer eingeben, der nie als null gesehen werden kann.
  • mehrere Race-Produkte (der Getter) sind immer logisch identisch, so dass es egal ist, welchen ein bestimmter Rennfahrer (oder später nicht-rennender Caller) zufällig abholt, oder sogar, ob irgendein Racer den einen verwendet selbst produziert;
  • jeder Installationstausch ist ein einzelner Speicher der Größe IntPtr , der garantiert atomar für jede entsprechende Plattform-Bitness ist;
  • Schließlich, und technisch absolut entscheidend für die perfekte formale Korrektheit , werden Arbeitsprodukte der "Verlierer" von GC zurückgewonnen und ergeben somit kein Leck. In dieser Art von Rennen sind die Verlierer alle Racer mit Ausnahme des last Finisher (da die Bemühungen aller anderen munter und summarisch mit einem identischen Ergebnis überschrieben werden).

Obwohl ich glaube, dass diese Punkte den Code unter allen möglichen Umständen vollständig schützen, wenn Sie immer noch misstrauisch oder argwöhnisch bezüglich der allgemeinen Schlussfolgerung sind, können Sie jederzeit eine zusätzliche Ebene des "bulletproofing" hinzufügen:

%Vor%

Es ist nur eine paranoide Version. Wie bei dem früheren verdichteten Einstrich, dem.Das NET-Speichermodell garantiert, dass es genau einen Speicher - und null Abrufe - für den Speicherort von '__get' gibt. (Das vollständig erweiterte Beispiel oben führt zwar einen zusätzlichen Hauptspeicherabruf durch, ist aber dank des zweiten Aufzählungszeichens immer noch gut). Wie ich bereits erwähnt habe, sollte das alles für die Korrektheit nicht notwendig sein, aber theoretisch könnte es ein winziges Problem ergeben Performance-Bonus: Durch das abschliessende Beenden des Rennens früher könnte der aggressive Flush in einem extrem seltenen Fall verhindern, dass ein nachfolgender Aufrufer auf einer schmutzigen Cache-Zeile unnötig (aber wieder harmlos) rast.

>

Doppel-Thunking

Aufrufe in die endgültige, hyper-schnelle Methode werden immer noch durch die zuvor gezeigten statischen Erweiterungsmethoden getunkelt. Dies liegt daran, dass wir auch Einstiegspunkte darstellen müssen, die zur Kompilierungszeit tatsächlich existieren, damit der Compiler an Metadaten bindet und diese propagiert. Das Double-Thunk ist ein kleiner Preis für die überwältigende Bequemlichkeit von stark typisierten Metadaten und Intellisense in der IDE für benutzerdefinierten Code, der bis zur Laufzeit nicht aufgelöst werden kann. Dennoch läuft es mindestens so schnell wie statisch kompilierter Code, way schneller, als bei jedem Aufruf eine Menge Reflektion zu machen, damit wir das Beste aus beiden Welten herausholen können!

    
Glenn Slayden 08.07.2017 12:59
quelle