Welches ist der einfachste Weg, um ein Label
anderes zu aktualisieren Thread
?
Ich habe einen
Form
Lauf aufthread1
und von dort starte ich einen anderen Thread (thread2
).Während der
thread2
Verarbeitung einiger Dateien möchte ich eineLabel
auf demForm
mit dem aktuellen Status derthread2
Arbeit aktualisieren .
Wie könnte ich das machen?
c#
.net
multithreading
winforms
user-interface
Grausam
quelle
quelle
Antworten:
Für .NET 2.0 ist hier ein netter Code, den ich geschrieben habe, der genau das tut, was Sie wollen, und der für jede Eigenschaft auf einem funktioniert
Control
:Nennen Sie es so:
Wenn Sie .NET 3.0 oder höher verwenden, können Sie die obige Methode als Erweiterungsmethode der
Control
Klasse umschreiben , wodurch der Aufruf von:UPDATE 10.05.2010:
Für .NET 3.0 sollten Sie diesen Code verwenden:
die LINQ- und Lambda-Ausdrücke verwendet, um eine viel sauberere, einfachere und sicherere Syntax zu ermöglichen:
Der Eigenschaftsname wird jetzt nicht nur zur Kompilierungszeit überprüft, sondern auch der Typ der Eigenschaft. Daher ist es unmöglich, einer booleschen Eigenschaft (beispielsweise) einen Zeichenfolgenwert zuzuweisen und damit eine Laufzeitausnahme auszulösen.
Leider hindert dies niemanden daran, dumme Dinge zu tun, wie z. B. das
Control
Eigentum und den Wert eines anderen weiterzugeben. Daher wird Folgendes gerne kompiliert:Daher habe ich die Laufzeitprüfungen hinzugefügt, um sicherzustellen, dass die übergebene Eigenschaft tatsächlich zu der Eigenschaft gehört, auf die
Control
die Methode aufgerufen wird. Nicht perfekt, aber immer noch viel besser als die .NET 2.0-Version.Wenn jemand weitere Vorschläge zur Verbesserung dieses Codes für die Sicherheit beim Kompilieren hat, kommentieren Sie dies bitte!
quelle
SetControlPropertyThreadSafe(myLabel, "Text", status)
von einem anderen Modul oder einer anderen Klasse oder Form aufgerufen werdenDer einfachste Weg ist eine anonyme Methode, die übergeben wird an
Label.Invoke
:Beachten Sie, dass
Invoke
die Ausführung blockiert wird, bis sie abgeschlossen ist - dies ist synchroner Code. Die Frage bezieht sich nicht auf asynchronen Code, aber es gibt viele Inhalte zu Stack Overflow über das Schreiben von asynchronem Code, wenn Sie mehr darüber erfahren möchten.quelle
Lange Arbeit erledigen
Seit .NET 4.5 und C # 5.0 sollten Sie das aufgabenbasierte asynchrone Muster (TAP) zusammen mit asynchronem verwenden - warten Sie auf Schlüsselwörter in allen Bereichen (einschließlich der GUI):
anstelle des asynchronen Programmiermodells (APM) und des ereignisbasierten asynchronen Musters (EAP) (letzteres umfasst die BackgroundWorker-Klasse ).
Dann ist die empfohlene Lösung für die Neuentwicklung:
Asynchrone Implementierung eines Ereignishandlers (Ja, das ist alles):
Implementierung des zweiten Threads, der den UI-Thread benachrichtigt:
Beachten Sie Folgendes:
Finden Sie eine ausführlichere Beispiele: Die Zukunft von C #: Gute Sachen kommen zu denen , die ‚await‘ von Joseph Albahari .
Siehe auch zum UI-Threading-Modellkonzept .
Ausnahmen behandeln
Das folgende Snippet ist ein Beispiel für die Behandlung von Ausnahmen und das Umschalten der Schaltflächeneigenschaften
Enabled
, um mehrere Klicks während der Hintergrundausführung zu verhindern.quelle
SecondThreadConcern.LongWork()
eine Ausnahme ausgelöst wird, kann sie vom UI-Thread abgefangen werden? Dies ist übrigens ein ausgezeichneter Beitrag.Task.Delay(500).Wait()
? Was bringt es, eine Aufgabe zu erstellen, um nur den aktuellen Thread zu blockieren? Sie sollten niemals einen Thread-Pool-Thread blockieren!Variation von Marc Gravells einfachster Lösung für .NET 4:
Oder verwenden Sie stattdessen den Aktionsdelegierten:
Hier finden Sie einen Vergleich der beiden: MethodInvoker vs Action for Control.BeginInvoke
quelle
this.refresh()
Ungültigmachung erzwingen und die GUI neu streichen muss .. wenn es hilfreich ist ..Feuern und vergessen Sie die Erweiterungsmethode für .NET 3.5+
Dies kann mit der folgenden Codezeile aufgerufen werden:
quelle
@this
ist einfach der Variablenname, in diesem Fall der Verweis auf das aktuelle Steuerelement, das die Erweiterung aufruft. Sie können es in Quelle umbenennen oder was auch immer Ihr Boot schwimmt. Ich verwende@this
, weil es sich auf "dieses Steuerelement" bezieht, das die Erweiterung aufruft und (zumindest in meinem Kopf) mit der Verwendung des Schlüsselworts "this" im normalen Code (ohne Erweiterung) übereinstimmt.OnUIThread
eher nennen alsUIThread
.RunOnUiThread
. Aber das ist nur persönlicher Geschmack.Dies ist der klassische Weg, wie Sie dies tun sollten:
Ihr Arbeitsthread hat ein Ereignis. Ihr UI-Thread startet einen anderen Thread, um die Arbeit zu erledigen, und verbindet dieses Worker-Ereignis, damit Sie den Status des Worker-Threads anzeigen können.
Dann müssen Sie in der Benutzeroberfläche Threads kreuzen, um das eigentliche Steuerelement zu ändern ... wie eine Beschriftung oder einen Fortschrittsbalken.
quelle
Die einfache Lösung ist zu verwenden
Control.Invoke
.quelle
Threading-Code ist oft fehlerhaft und immer schwer zu testen. Sie müssen keinen Threading-Code schreiben, um die Benutzeroberfläche von einer Hintergrundaufgabe aus zu aktualisieren. Verwenden Sie einfach die BackgroundWorker- Klasse, um die Aufgabe auszuführen , und die ReportProgress- Methode, um die Benutzeroberfläche zu aktualisieren. Normalerweise melden Sie nur einen Prozentsatz als abgeschlossen, aber es gibt eine weitere Überladung, die ein Statusobjekt enthält. Hier ist ein Beispiel, das nur ein Zeichenfolgenobjekt meldet:
Das ist in Ordnung, wenn Sie immer dasselbe Feld aktualisieren möchten. Wenn Sie kompliziertere Aktualisierungen vornehmen müssen, können Sie eine Klasse definieren, die den UI-Status darstellt, und diese an die ReportProgress-Methode übergeben.
Als letztes müssen Sie das
WorkerReportsProgress
Flag setzen,ReportProgress
sonst wird die Methode vollständig ignoriert.quelle
backgroundWorker1_RunWorkerCompleted
.Die überwiegende Mehrheit der Antworten verwendet,
Control.Invoke
was eine Rennbedingung ist, die darauf wartet, passiert zu werden . Betrachten Sie zum Beispiel die akzeptierte Antwort:Wenn der Benutzer das Formular kurz vor dem
this.Invoke
Aufruf schließt (denken Sie daran,this
ist dasForm
Objekt),ObjectDisposedException
wird wahrscheinlich ein ausgelöst.Die Lösung ist die Verwendung
SynchronizationContext
, insbesondereSynchronizationContext.Current
wie von hamilton.danielb vorgeschlagen (andere Antworten beruhen auf bestimmtenSynchronizationContext
Implementierungen, was völlig unnötig ist). Ich würde seinen Code leicht modifizieren, um ihn zu verwenden,SynchronizationContext.Post
anstatt ihn zu verwendenSynchronizationContext.Send
(da der Arbeitsthread normalerweise nicht warten muss):Beachten Sie, dass Sie unter .NET 4.0 und höher wirklich Aufgaben für asynchrone Vorgänge verwenden sollten. In der Antwort von n-san finden Sie den entsprechenden aufgabenbasierten Ansatz (unter Verwendung
TaskScheduler.FromCurrentSynchronizationContext
).Schließlich können Sie unter .NET 4.5 und höher auch
Progress<T>
(was im WesentlichenSynchronizationContext.Current
bei seiner Erstellung erfasst wird) verwenden, wie von Ryszard Dżegan's für Fälle demonstriert, in denen der lang laufende Vorgang UI-Code ausführen muss, während er noch arbeitet.quelle
Sie müssen sicherstellen, dass das Update auf dem richtigen Thread erfolgt. der UI-Thread.
Dazu müssen Sie den Event-Handler aufrufen, anstatt ihn direkt aufzurufen.
Sie können dies tun, indem Sie Ihre Veranstaltung wie folgt auslösen:
(Der Code wird hier aus meinem Kopf heraus eingegeben, daher habe ich nicht nach korrekter Syntax usw. gesucht, aber er sollte Sie zum Laufen bringen.)
Beachten Sie, dass der obige Code in WPF-Projekten nicht funktioniert, da WPF-Steuerelemente die
ISynchronizeInvoke
Schnittstelle nicht implementieren .Um sicher zu stellen , dass der Code über Arbeiten mit Windows Forms und WPF, und alle anderen Plattformen, Sie einen Blick auf die haben kann
AsyncOperation
,AsyncOperationManager
undSynchronizationContext
Klassen.Um Ereignisse auf diese Weise einfach auszulösen, habe ich eine Erweiterungsmethode erstellt, mit der ich das Auslösen eines Ereignisses vereinfachen kann, indem ich einfach Folgendes aufrufe:
Natürlich können Sie auch die BackGroundWorker-Klasse verwenden, die diese Angelegenheit für Sie abstrahiert.
quelle
Sie müssen die Methode im GUI-Thread aufrufen. Sie können dies tun, indem Sie Control.Invoke aufrufen.
Zum Beispiel:
quelle
Aufgrund der Trivialität des Szenarios hätte ich tatsächlich die UI-Thread-Abfrage für den Status. Ich denke, Sie werden feststellen, dass es sehr elegant sein kann.
Der Ansatz vermeidet den Marshalling-Vorgang, der bei Verwendung der Methoden
ISynchronizeInvoke.Invoke
und erforderlich istISynchronizeInvoke.BeginInvoke
. Es ist nichts Falsches daran, die Marshalling-Technik zu verwenden, aber es gibt ein paar Vorsichtsmaßnahmen, die Sie beachten müssen.BeginInvoke
zu häufig anrufen, da sonst die Nachrichtenpumpe überlaufen könnte.Invoke
des Worker-Threads ist ein blockierender Aufruf. Die in diesem Thread ausgeführte Arbeit wird vorübergehend angehalten.Die Strategie, die ich in dieser Antwort vorschlage, kehrt die Kommunikationsrollen der Threads um. Anstatt dass der Worker-Thread die Daten pusht, fragt der UI-Thread danach. Dies ist ein häufiges Muster, das in vielen Szenarien verwendet wird. Da Sie lediglich Fortschrittsinformationen aus dem Worker-Thread anzeigen möchten, werden Sie feststellen, dass diese Lösung eine hervorragende Alternative zur Marshalling-Lösung darstellt. Es hat die folgenden Vorteile.
Control.Invoke
oderControl.BeginInvoke
Ansatz sie die fest Paare.quelle
Elapsed
Ereignisse zu verwenden, verwenden Sie eine Member-Methode, damit Sie den Timer entfernen können, wenn das Formular entsorgt wird ...System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };
und zugewiesen überm_Timer.Elapsed += handler;
, später im Entsorgungskontext machem_Timer.Elapsed -= handler;
ich ein Recht? Und für die Entsorgung / Schließung folgen Sie den hier besprochenen Ratschlägen .Keines der Invoke-Elemente in den vorherigen Antworten ist erforderlich.
Sie müssen sich WindowsFormsSynchronizationContext ansehen:
quelle
Diese Lösung ähnelt der obigen Lösung mit .NET Framework 3.0, hat jedoch das Problem der Sicherheitsunterstützung zur Kompilierungszeit gelöst .
Benutzen:
Der Compiler schlägt fehl, wenn der Benutzer den falschen Datentyp übergibt.
quelle
Salvete! Nachdem ich nach dieser Frage gesucht hatte, fand ich die Antworten von FrankG und Oregon Ghost am einfachsten und nützlichsten für mich. Jetzt habe ich in Visual Basic codiert und dieses Snippet durch einen Konverter ausgeführt. Ich bin mir also nicht ganz sicher, wie es ausgeht.
Ich habe ein Dialogformular mit dem Namen
form_Diagnostics,
richtext,updateDiagWindow,
das ich als eine Art Protokollierungsanzeige verwende. Ich musste in der Lage sein, seinen Text aus allen Threads zu aktualisieren. Durch die zusätzlichen Zeilen kann das Fenster automatisch zu den neuesten Zeilen scrollen.Und so kann ich jetzt die Anzeige mit einer Zeile von überall im gesamten Programm auf die Weise aktualisieren, von der Sie glauben, dass sie ohne Threading funktionieren würde:
Hauptcode (fügen Sie diesen in den Klassencode Ihres Formulars ein):
quelle
Für viele Zwecke ist es so einfach:
"serviceGUI ()" ist eine Methode auf GUI-Ebene innerhalb des Formulars (this), mit der beliebig viele Steuerelemente geändert werden können. Rufen Sie "updateGUI ()" aus dem anderen Thread auf. Parameter können hinzugefügt werden, um Werte zu übergeben, oder (wahrscheinlich schneller) nach Bedarf Klassenbereichsvariablen mit Sperren verwenden, wenn die Möglichkeit eines Konflikts zwischen Threads besteht, die auf sie zugreifen und Instabilität verursachen können. Verwenden Sie BeginInvoke anstelle von Invoke, wenn der Nicht-GUI-Thread zeitkritisch ist (unter Berücksichtigung der Warnung von Brian Gideon).
quelle
Dies in meiner C # 3.0-Variante von Ian Kemps Lösung:
Sie nennen es so:
Ansonsten ist das Original eine sehr schöne Lösung.
quelle
Beachten Sie, dass dies
BeginInvoke()
vorgezogen wird,Invoke()
da es weniger wahrscheinlich zu Deadlocks kommt (dies ist hier jedoch kein Problem, wenn Sie nur einem Etikett Text zuweisen):Bei der Verwendung
Invoke()
warten Sie auf die Rückkehr der Methode. Nun kann es sein, dass Sie im aufgerufenen Code etwas tun, das auf den Thread warten muss, was möglicherweise nicht sofort offensichtlich ist, wenn er in einigen von Ihnen aufgerufenen Funktionen vergraben ist, was selbst indirekt über Ereignishandler geschehen kann. Sie würden also auf den Thread warten, der Thread würde auf Sie warten und Sie sind festgefahren.Dies führte tatsächlich dazu, dass einige unserer veröffentlichten Software hängen blieben. Es war leicht genug , um fix durch den Ersatz
Invoke()
mitBeginInvoke()
. Verwenden Sie die Option, es sei denn, Sie benötigen einen Synchronbetrieb. Dies kann der Fall sein, wenn Sie einen Rückgabewert benötigenBeginInvoke()
.quelle
Als ich auf dasselbe Problem stieß, suchte ich Hilfe bei Google, aber anstatt mir eine einfache Lösung zu geben, verwirrte es mich mehr, indem ich Beispiele für
MethodInvoker
und bla bla bla gab. Also habe ich beschlossen, es selbst zu lösen. Hier ist meine Lösung:Machen Sie einen Delegierten wie folgt:
Sie können diese Funktion in einem neuen Thread wie diesem aufrufen
Sei nicht verwechselt mit
Thread(() => .....)
. Ich verwende eine anonyme Funktion oder einen Lambda-Ausdruck, wenn ich an einem Thread arbeite. Um die Codezeilen zu reduzieren, können Sie auch dieThreadStart(..)
Methode verwenden, die ich hier nicht erklären soll.quelle
Verwenden Sie einfach so etwas:
quelle
e.ProgressPercentage
ja, befinden Sie sich nicht bereits im UI-Thread der Methode, die Sie aufrufen?Sie können den bereits vorhandenen Delegaten verwenden
Action
:quelle
Meine Version besteht darin, eine Zeile des rekursiven "Mantras" einzufügen :
Für keine Argumente:
Für eine Funktion mit Argumenten:
DAS IST ES .
Einige Argumente : Normalerweise ist es für die Lesbarkeit des Codes schlecht, {} nach einer
if ()
Anweisung in eine Zeile zu setzen. Aber in diesem Fall ist es Routine "Mantra". Die Lesbarkeit des Codes wird nicht beeinträchtigt, wenn diese Methode über das Projekt hinweg konsistent ist. Und es schützt Ihren Code vor Müll (eine Codezeile statt fünf).Wie Sie sehen
if(InvokeRequired) {something long}
, wissen Sie nur "diese Funktion kann sicher von einem anderen Thread aufgerufen werden".quelle
Versuchen Sie, das Etikett damit zu aktualisieren
quelle
Erstellen Sie eine Klassenvariable:
Legen Sie es im Konstruktor fest, der Ihre Benutzeroberfläche erstellt:
Wenn Sie das Etikett aktualisieren möchten:
quelle
Sie müssen invoke und delegate verwenden
quelle
Die meisten anderen Antworten sind für mich in dieser Frage etwas komplex (ich bin neu in C #), daher schreibe ich meine:
Ich habe eine WPF- Anwendung und habe einen Worker wie folgt definiert:
Problem:
Lösung:
Ich muss noch herausfinden, was die obige Zeile bedeutet, aber es funktioniert.
Für WinForms :
Lösung:
quelle
Der einfachste Weg, den ich denke:
quelle
Greifen Sie beispielsweise auf ein anderes Steuerelement als im aktuellen Thread zu:
Dort
lblThreshold
ist das ein Label undSpeed_Threshold
eine globale Variable.quelle
Wenn Sie sich im UI-Thread befinden, können Sie ihn nach seinem Taskplaner für den Synchronisationskontext fragen. Sie erhalten einen TaskScheduler , der alles im UI-Thread plant.
Anschließend können Sie Ihre Aufgaben so verketten, dass eine andere Aufgabe (die im UI-Thread geplant ist) sie auswählt und einem Label zuweist, wenn das Ergebnis fertig ist.
Dies funktioniert für Aufgaben (nicht für Threads), die derzeit die bevorzugte Methode zum Schreiben von gleichzeitigem Code sind .
quelle
Task.Start
ist normalerweise keine bewährte Methode. Blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspxIch habe gerade die Antworten gelesen und dies scheint ein sehr heißes Thema zu sein. Ich verwende derzeit .NET 3.5 SP1 und Windows Forms.
Die in den vorherigen Antworten ausführlich beschriebene bekannte Formel, die die InvokeRequired- Eigenschaft verwendet, deckt die meisten Fälle ab, jedoch nicht den gesamten Pool.
Was ist, wenn der Griff noch nicht erstellt wurde?
Die InvokeRequired- Eigenschaft, wie hier beschrieben (Control.InvokeRequired-Eigenschaftsreferenz auf MSDN), gibt true zurück, wenn der Aufruf von einem Thread aus erfolgt, der nicht der GUI-Thread ist, false, entweder wenn der Aufruf vom GUI-Thread erfolgt ist oder wenn das Handle ausgeführt wurde noch nicht erstellt.
Sie können auf eine Ausnahme stoßen, wenn ein modales Formular von einem anderen Thread angezeigt und aktualisiert werden soll. Da dieses Formular modal angezeigt werden soll, können Sie Folgendes tun:
Und der Delegat kann ein Label auf der GUI aktualisieren:
Dies kann eine verursachen InvalidOperationException , wenn die Vorgänge vor dem Update des Labels „weniger Zeit in Anspruch nehmen“ (lesen Sie es und es als eine Vereinfachung interpretieren) als die Zeit, die für den GUI - Thread nimmt die erstellen Formular ‚s Griff . Dies geschieht innerhalb der ShowDialog () -Methode.
Sie sollten auch wie folgt nach dem Griff suchen :
Sie können den auszuführenden Vorgang ausführen, wenn das Handle noch nicht erstellt wurde: Sie können das GUI-Update einfach ignorieren (wie im obigen Code gezeigt) oder warten (riskanter). Dies sollte die Frage beantworten.
Optionales Material: Persönlich habe ich Folgendes programmiert:
Ich füttere meine Formulare, die von einem anderen Thread aktualisiert werden, mit einer Instanz dieses ThreadSafeGuiCommand und definiere Methoden, die die GUI (in meinem Formular) wie folgt aktualisieren:
Auf diese Weise bin ich mir ziemlich sicher, dass ich meine GUI aktualisieren werde, unabhängig davon, welcher Thread den Anruf tätigt, und optional auf eine genau definierte Zeitspanne (das Timeout) warten wird.
quelle