In dem Buch Programming C # enthält es einen Beispielcode über SynchronizationContext
:
SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
string text = File.ReadAllText(@"c:\temp\log.txt");
originalContext.Post(delegate {
myTextBox.Text = text;
}, null);
});
Ich bin ein Anfänger in Threads, bitte antworten Sie im Detail. Erstens weiß ich nicht, was Kontext bedeutet, was speichert das Programm in der originalContext
? Und Post
was macht der UI-Thread, wenn die Methode ausgelöst wird?
Wenn ich ein paar dumme Dinge frage, korrigiere mich bitte, danke!
EDIT: Was ist zum Beispiel, wenn ich nur myTextBox.Text = text;
in die Methode schreibe , was ist der Unterschied?
c#
.net
multithreading
cloudyFan
quelle
quelle
async
/await
auf verlässt sichSynchronizationContext
darunter.Antworten:
Einfach ausgedrückt,
SynchronizationContext
stellt einen Ort dar, an dem Code ausgeführt werden könnte. Delegaten, die an die MethodeSend
oder übergebenPost
werden, werden dann an dieser Stelle aufgerufen. (Post
ist die nicht blockierende / asynchrone Version vonSend
.)Jedem Thread kann eine
SynchronizationContext
Instanz zugeordnet sein. Der laufende Thread kann durch Aufrufen der statischenSynchronizationContext.SetSynchronizationContext
Methode einem Synchronisationskontext zugeordnet werden , und der aktuelle Kontext des laufenden Threads kann über dieSynchronizationContext.Current
Eigenschaft abgefragt werden .Trotz allem, was ich gerade geschrieben habe (jeder Thread hat einen zugeordneten Synchronisationskontext),
SynchronizationContext
repräsentiert a nicht unbedingt einen bestimmten Thread . Es kann auch den Aufruf der an ihn übergebenen Delegaten an einen von mehreren Threads (z. B. an einenThreadPool
Worker-Thread) oder (zumindest theoretisch) an einen bestimmten CPU-Kern oder sogar an einen anderen Netzwerkhost weiterleiten . Wo Ihre Delegierten am Ende ausgeführt werden, hängt von der Art derSynchronizationContext
Verwendung ab.Windows Forms installiert ein
WindowsFormsSynchronizationContext
auf dem Thread, auf dem das erste Formular erstellt wird. (Dieser Thread wird üblicherweise als "UI-Thread" bezeichnet.) Diese Art von Synchronisationskontext ruft die an genau diesen Thread übergebenen Delegaten auf. Dies ist sehr nützlich, da Windows Forms wie viele andere UI-Frameworks nur die Manipulation von Steuerelementen in demselben Thread zulässt, in dem sie erstellt wurden.Der Code, an den Sie übergeben haben,
ThreadPool.QueueUserWorkItem
wird in einem Thread-Pool-Worker-Thread ausgeführt. Das heißt, es wird nicht auf dem Thread ausgeführt, auf dem SiemyTextBox
erstellt wurden, sodass Windows Forms früher oder später (insbesondere in Release-Builds) eine Ausnahme auslöst, die Sie darauf hinweist, dass Sie möglicherweise nichtmyTextBox
über einen anderen Thread zugreifen können .Aus diesem Grund müssen Sie
myTextBox
vor dieser bestimmten Zuweisung vom Worker-Thread zum "UI-Thread" (wo er erstellt wurde) "zurückschalten" . Dies geschieht wie folgt:Während Sie sich noch im UI-Thread befinden, erfassen Sie dort Windows Forms
SynchronizationContext
und speichern Sie einen Verweis darauf in einer Variablen (originalContext
) zur späteren Verwendung. Sie müssenSynchronizationContext.Current
an dieser Stelle abfragen . Wenn Sie es innerhalb des übergebenen Codes abgefragt habenThreadPool.QueueUserWorkItem
, erhalten Sie möglicherweise den Synchronisationskontext, der dem Arbeitsthread des Thread-Pools zugeordnet ist. Sobald Sie einen Verweis auf den Kontext von Windows Forms gespeichert haben, können Sie ihn überall und jederzeit verwenden, um Code an den UI-Thread zu "senden".Wenn Sie ein UI-Element bearbeiten müssen (sich aber nicht mehr im UI-Thread befinden oder möglicherweise nicht mehr befinden), greifen Sie über auf den Synchronisierungskontext von Windows Forms zu
originalContext
und geben Sie den Code, der die Benutzeroberfläche manipuliert, entweder anSend
oder weiterPost
.Schlussbemerkungen und Hinweise:
Was Synchronisationskontexte für Sie nicht tun, ist Ihnen zu sagen, welcher Code an einem bestimmten Ort / Kontext ausgeführt werden muss und welcher Code nur normal ausgeführt werden kann, ohne ihn an a zu übergeben
SynchronizationContext
. Um dies zu entscheiden, müssen Sie die Regeln und Anforderungen des Frameworks kennen, gegen das Sie programmieren - in diesem Fall Windows Forms.Denken Sie also an diese einfache Regel für Windows Forms: Greifen Sie NICHT von einem anderen Thread als dem, der sie erstellt hat, auf Steuerelemente oder Formulare zu. Wenn Sie dies tun müssen, verwenden Sie den
SynchronizationContext
oben beschriebenen Mechanismus oderControl.BeginInvoke
(eine Windows Forms-spezifische Methode, um genau dasselbe zu tun).Wenn Sie die Programmierung gegen .NET 4.5 oder höher, können Sie Ihr Leben viel einfacher machen , indem Sie den Code umzuwandeln , dass explizit Verwendungen
SynchronizationContext
,ThreadPool.QueueUserWorkItem
,control.BeginInvoke
etc. auf den neuenasync
/await
Keywords und die Task - Library (TPL) Parallel , dh die API Umgebung dieTask
undTask<TResult>
Klassen. Diese kümmern sich zu einem sehr hohen Grad darum, den Synchronisationskontext des UI-Threads zu erfassen, eine asynchrone Operation zu starten und dann wieder zum UI-Thread zurückzukehren, damit Sie das Ergebnis der Operation verarbeiten können.quelle
Application.Run
IIRC gestartet wird ). Dies ist ein ziemlich fortgeschrittenes Thema und nicht beiläufig.null
) oder eine Instanz vonSynchronizationContext
(oder eine Unterklasse davon). Der Punkt dieses Zitats war nicht das, was Sie erhalten, sondern das, was Sie nicht erhalten: der Synchronisationskontext des UI-Threads.Ich möchte zu anderen Antworten hinzufügen,
SynchronizationContext.Post
nur einen Rückruf für die spätere Ausführung im Ziel-Thread in die Warteschlange stellen (normalerweise während des nächsten Zyklus der Nachrichtenschleife des Ziel-Threads) und dann die Ausführung im aufrufenden Thread fortsetzen. Versucht andererseits,SynchronizationContext.Send
den Rückruf auf dem Zielthread sofort auszuführen, wodurch der aufrufende Thread blockiert wird und ein Deadlock auftreten kann. In beiden Fällen besteht die Möglichkeit einer erneuten Codeeingabe (Eingabe einer Klassenmethode im selben Ausführungsthread, bevor der vorherige Aufruf derselben Methode zurückgegeben wurde).Wenn Sie mit Win32 - Programmiermodell vertraut sind, wäre eine sehr enge Analogie sein
PostMessage
undSendMessage
APIs, die Sie aufrufen können , eine Nachricht von einem Faden unterscheidet sich von dem Zielfenster ein versenden.Hier ist eine sehr gute Erklärung für Synchronisationskontexte: Alles dreht sich um den Synchronisationskontext .
quelle
Es speichert den Synchronisationsanbieter, eine von SynchronizationContext abgeleitete Klasse. In diesem Fall handelt es sich wahrscheinlich um eine Instanz von WindowsFormsSynchronizationContext. Diese Klasse verwendet die Methoden Control.Invoke () und Control.BeginInvoke (), um die Methoden Send () und Post () zu implementieren. Oder es kann DispatcherSynchronizationContext sein, es verwendet Dispatcher.Invoke () und BeginInvoke (). In einer Winforms- oder WPF-App wird dieser Anbieter automatisch installiert, sobald Sie ein Fenster erstellen.
Wenn Sie Code in einem anderen Thread ausführen, wie z. B. dem im Snippet verwendeten Thread-Pool-Thread, müssen Sie darauf achten, dass Sie keine direkt threadsicheren Objekte verwenden. Wie bei jedem Benutzeroberflächenobjekt müssen Sie die Eigenschaft TextBox.Text aus dem Thread aktualisieren, der die TextBox erstellt hat. Die Post () -Methode stellt sicher, dass das Delegatenziel auf diesem Thread ausgeführt wird.
Beachten Sie, dass dieses Snippet etwas gefährlich ist. Es funktioniert nur dann ordnungsgemäß, wenn Sie es über den UI-Thread aufrufen. SynchronizationContext.Current hat unterschiedliche Werte in unterschiedlichen Threads. Nur der UI-Thread hat einen verwendbaren Wert. Und ist der Grund, warum der Code es kopieren musste. Eine besser lesbare und sicherere Möglichkeit, dies in einer Winforms-App zu tun:
Was den Vorteil hat, dass es funktioniert, wenn es von einem beliebigen Thread aufgerufen wird. Der Vorteil der Verwendung von SynchronizationContext.Current besteht darin, dass es weiterhin funktioniert, unabhängig davon, ob der Code in Winforms oder WPF verwendet wird. Dies ist in einer Bibliothek von Bedeutung. Dies ist sicherlich kein gutes Beispiel für einen solchen Code. Sie wissen immer, welche Art von TextBox Sie hier haben, sodass Sie immer wissen, ob Sie Control.BeginInvoke oder Dispatcher.BeginInvoke verwenden sollen. Tatsächlich ist die Verwendung von SynchronizationContext.Current nicht so häufig.
Das Buch versucht, Ihnen das Einfädeln beizubringen, daher ist es in Ordnung, dieses fehlerhafte Beispiel zu verwenden. In der Praxis können Sie in den wenigen Fällen, in denen Sie die Verwendung von SynchronizationContext.Current in Betracht ziehen, die Schlüsselwörter async / await von C # oder TaskScheduler.FromCurrentSynchronizationContext () für Sie verwenden. Beachten Sie jedoch, dass sie sich aus genau demselben Grund immer noch schlecht verhalten wie das Snippet, wenn Sie sie im falschen Thread verwenden. Eine sehr häufige Frage ist, dass die zusätzliche Abstraktionsebene nützlich ist, es jedoch schwieriger macht, herauszufinden, warum sie nicht richtig funktionieren. Hoffentlich sagt dir das Buch auch, wann du es nicht benutzen sollst :)
quelle
Der Zweck des Synchronisationskontexts besteht darin, sicherzustellen, dass er
myTextbox.Text = text;
im Haupt-UI-Thread aufgerufen wird.Windows erfordert, dass auf GUI-Steuerelemente nur von dem Thread zugegriffen wird, mit dem sie erstellt wurden. Wenn Sie versuchen, den Text in einem Hintergrundthread zuzuweisen, ohne ihn zuvor zu synchronisieren (auf verschiedene Weise, z. B. durch dieses oder das Invoke-Muster), wird eine Ausnahme ausgelöst.
Dadurch wird der Synchronisationskontext vor dem Erstellen des Hintergrundthreads gespeichert. Anschließend verwendet der Hintergrundthread den Kontext. Die Post-Methode führt den GUI-Code aus.
Ja, der von Ihnen angezeigte Code ist grundsätzlich nutzlos. Warum einen Hintergrund-Thread erstellen, um sofort zum Haupt-UI-Thread zurückkehren zu müssen? Es ist nur ein Beispiel.
quelle
Zur Quelle
Beispiel: Angenommen, Sie haben zwei Threads, Thread1 und Thread2. Angenommen, Thread1 erledigt einige Arbeiten, und dann möchte Thread1 Code auf Thread2 ausführen. Eine Möglichkeit, dies zu tun, besteht darin, Thread2 nach seinem SynchronizationContext-Objekt zu fragen, es Thread1 zu geben, und Thread1 kann dann SynchronizationContext.Send aufrufen, um den Code auf Thread2 auszuführen.
quelle
SynchronizationContext bietet uns die Möglichkeit, eine Benutzeroberfläche von einem anderen Thread aus zu aktualisieren (synchron über die Send-Methode oder asynchron über die Post-Methode).
Schauen Sie sich das folgende Beispiel an:
SynchronizationContext.Current gibt den Synchronisierungskontext des UI-Threads zurück. Woher weiß ich das? Zu Beginn jedes Formulars oder jeder WPF-App wird der Kontext im UI-Thread festgelegt. Wenn Sie eine WPF-App erstellen und mein Beispiel ausführen, werden Sie feststellen, dass beim Klicken auf die Schaltfläche etwa 1 Sekunde lang der Ruhezustand angezeigt wird und dann der Inhalt der Datei angezeigt wird. Sie können davon ausgehen, dass dies nicht der Fall ist, da der Aufrufer der UpdateTextBox-Methode (Work1) eine Methode ist, die an einen Thread übergeben wird. Daher sollte dieser Thread nicht der Haupt-UI-Thread NOPE! Beachten Sie, dass die Work1-Methode, obwohl sie an einen Thread übergeben wird, auch ein Objekt akzeptiert, bei dem es sich um den SyncContext handelt. Wenn Sie es sich ansehen, werden Sie feststellen, dass die UpdateTextBox-Methode über die syncContext.Post-Methode und nicht über die Work1-Methode ausgeführt wird. Schauen Sie sich Folgendes an:
Das letzte Beispiel und dieses führen dasselbe aus. Beide blockieren die Benutzeroberfläche nicht, während sie Jobs ausführt.
Stellen Sie sich SynchronizationContext abschließend als Thread vor. Es ist kein Thread, es definiert einen Thread (Beachten Sie, dass nicht jeder Thread einen SyncContext hat). Wann immer wir die Post- oder Send-Methode aufrufen, um eine Benutzeroberfläche zu aktualisieren, ist es so, als würde man die Benutzeroberfläche normalerweise über den Haupt-UI-Thread aktualisieren. Wenn Sie aus bestimmten Gründen die Benutzeroberfläche von einem anderen Thread aus aktualisieren müssen, stellen Sie sicher, dass der Thread den SyncContext des Haupt-UI-Threads enthält, und rufen Sie einfach die Send- oder Post-Methode mit der Methode auf, die Sie ausführen möchten, und Sie sind alle einstellen.
Hoffe das hilft dir, Kumpel!
quelle
SynchronizationContext ist im Wesentlichen ein Anbieter für die Ausführung von Rückrufdelegierten , der hauptsächlich dafür verantwortlich ist, dass die Delegaten in einem bestimmten Ausführungskontext ausgeführt werden, nachdem ein bestimmter Codeteil (der in einem Taskobjekt von .Net TPL enthalten ist) eines Programms seine Ausführung abgeschlossen hat.
Aus technischer Sicht ist SC eine einfache C # -Klasse, die darauf ausgerichtet ist, ihre Funktion speziell für Task Parallel Library-Objekte zu unterstützen und bereitzustellen.
Jede .NET-Anwendung mit Ausnahme von Konsolenanwendungen verfügt über eine bestimmte Implementierung dieser Klasse, die auf dem spezifischen zugrunde liegenden Framework basiert, z. B.: WPF, WindowsForm, Asp Net, Silverlight usw.
Die Bedeutung dieses Objekts hängt mit der Synchronisation zwischen den Ergebnissen der asynchronen Ausführung von Code und der Ausführung des abhängigen Codes zusammen, der auf die Ergebnisse dieser asynchronen Arbeit wartet.
Und das Wort "Kontext" steht für Ausführungskontext, dh den aktuellen Ausführungskontext, in dem dieser wartende Code ausgeführt wird, nämlich die Synchronisation zwischen asynchronem Code und seinem wartenden Code in einem bestimmten Ausführungskontext. Daher heißt dieses Objekt SynchronizationContext: Es stellt den Ausführungskontext dar, der die Synchronisierung des asynchronen Codes und die Ausführung des wartenden Codes übernimmt .
quelle
Dieses Beispiel stammt aus Linqpad-Beispielen von Joseph Albahari, aber es hilft wirklich zu verstehen, was der Synchronisationskontext bewirkt.
quelle