Ich habe ein Szenario. (Windows Forms, C #, .NET)
- Es gibt ein Hauptformular, in dem einige Benutzersteuerelemente gehostet werden.
- Das Benutzersteuerelement führt einige
UserControl_Load
umfangreiche Datenoperationen aus , sodass die Benutzeroberfläche für die Dauer der Ausführung der Lademethode nicht mehr reagiert, wenn ich die Methode direkt aufrufe. - Um dies zu überwinden, lade ich Daten in einen anderen Thread (versuche, vorhandenen Code so wenig wie möglich zu ändern).
- Ich habe einen Hintergrund-Worker-Thread verwendet, der die Daten lädt und die Anwendung benachrichtigt, wenn sie fertig ist.
- Jetzt kam ein echtes Problem. Die gesamte Benutzeroberfläche (Hauptformular und seine untergeordneten Benutzersteuerelemente) wurde im primären Hauptthread erstellt. In der LOAD-Methode der Benutzersteuerung rufe ich Daten basierend auf den Werten eines Steuerelements (wie z. B. eines Textfelds) in userControl ab.
Der Pseudocode würde folgendermaßen aussehen:
CODE 1
UserContrl1_LoadDataMethod()
{
if (textbox1.text == "MyName") // This gives exception
{
//Load data corresponding to "MyName".
//Populate a globale variable List<string> which will be binded to grid at some later stage.
}
}
Die Ausnahme war
Threadübergreifende Operation ungültig: Steuerelement, auf das von einem anderen Thread als dem Thread zugegriffen wird, für den es erstellt wurde.
Um mehr darüber zu erfahren, habe ich ein bisschen gegoogelt und ein Vorschlag kam wie der folgende Code
CODE 2
UserContrl1_LoadDataMethod()
{
if (InvokeRequired) // Line #1
{
this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
return;
}
if (textbox1.text == "MyName") // Now it wont give an exception
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be binded to grid at some later stage
}
}
ABER ABER ABER ... es scheint, ich bin wieder auf dem ersten Platz. Die Anwendung reagiert erneut nicht mehr. Es scheint an der Ausführung von Zeile 1 zu liegen, wenn Bedingung. Die Ladeaufgabe wird wieder vom übergeordneten Thread ausgeführt und nicht vom dritten, den ich erzeugt habe.
Ich weiß nicht, ob ich das richtig oder falsch wahrgenommen habe. Ich bin neu im Threading.
Wie löse ich das und was bewirkt die Ausführung von Zeile 1, wenn blockiert?
Die Situation ist dies : Ich möchte Daten basierend auf dem Wert eines Steuerelements in eine globale Variable laden. Ich möchte den Wert eines Steuerelements im untergeordneten Thread nicht ändern. Ich werde es nie von einem Kinder-Thread aus tun.
Greifen Sie also nur auf den Wert zu, damit die entsprechenden Daten aus der Datenbank abgerufen werden können.
quelle
Antworten:
Wie pro Prerak K-Update Kommentar (seit gelöscht):
Die gewünschte Lösung sollte dann folgendermaßen aussehen:
Führen Sie Ihre ernsthafte Verarbeitung im separaten Thread durch, bevor Sie versuchen, zum Thread des Steuerelements zurückzukehren. Zum Beispiel:
quelle
Threading-Modell in der Benutzeroberfläche
Bitte lesen Sie das Threading-Modell in UI-Anwendungen, um grundlegende Konzepte zu verstehen. Der Link navigiert zu einer Seite, die das WPF-Threading-Modell beschreibt. Windows Forms verwendet jedoch dieselbe Idee.
Der UI-Thread
BeginInvoke- und Invoke-Methoden
Aufrufen
BeginInvoke
Codelösung
Antworten auf Frage lesen Wie aktualisiere ich die GUI von einem anderen Thread in C #? . Für C # 5.0 und .NET 4.5 die empfohlene Lösung ist hier .
quelle
Sie möchten nur
Invoke
oderBeginInvoke
für das Minimum an Arbeit verwenden, die zum Ändern der Benutzeroberfläche erforderlich ist. Ihre "schwere" Methode sollte auf einem anderen Thread ausgeführt werden (z. B. überBackgroundWorker
), aber dannControl.Invoke
/Control.BeginInvoke
nur zum Aktualisieren der Benutzeroberfläche verwenden. Auf diese Weise kann Ihr UI-Thread UI-Ereignisse usw. verarbeiten.In meinem Threading-Artikel finden Sie ein WinForms-Beispiel - obwohl der Artikel bereits geschrieben wurde, bevor er
BackgroundWorker
in die Szene kam, und ich fürchte, ich habe ihn in dieser Hinsicht nicht aktualisiert.BackgroundWorker
vereinfacht lediglich den Rückruf ein wenig.quelle
Ich weiß, dass es jetzt zu spät ist. Aber auch heute noch, wenn Sie Probleme beim Zugriff auf Cross-Thread-Steuerelemente haben? Dies ist die kürzeste Antwort bis heute: P.
So greife ich von einem Thread aus auf ein Formularsteuerelement zu.
quelle
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
. Ich habe es hierIch hatte dieses Problem mit dem
FileSystemWatcher
und stellte fest, dass der folgende Code das Problem löste:fsw.SynchronizingObject = this
Das Steuerelement verwendet dann das aktuelle Formularobjekt, um die Ereignisse zu verarbeiten, und befindet sich daher im selben Thread.
quelle
.SynchronizingObject = Me
Ich finde den Check-and-Invoke-Code, der in allen Methoden, die sich auf Formulare beziehen, verstreut sein muss, viel zu ausführlich und unnötig. Hier ist eine einfache Erweiterungsmethode, mit der Sie sie vollständig beseitigen können:
Und dann können Sie einfach Folgendes tun:
Kein Herumspielen mehr - einfach.
quelle
t
in diesem Fall wirdtextbox1
- es wird als Argument übergebenSteuerelemente in .NET sind im Allgemeinen nicht threadsicher. Das bedeutet, dass Sie nicht von einem anderen Thread als dem, auf dem es sich befindet, auf ein Steuerelement zugreifen sollten. Um dies zu umgehen , müssen Sie aufrufen das Steuerelement , was in Ihrem zweiten Beispiel versucht wird.
In Ihrem Fall müssen Sie jedoch nur die lang laufende Methode an den Hauptthread zurückgeben. Natürlich ist das nicht wirklich das, was Sie tun möchten. Sie müssen dies ein wenig überdenken, damit Sie im Haupt-Thread nur hier und da eine schnelle Eigenschaft festlegen.
quelle
Die sauberste (und richtige) Lösung für UI-Cross-Threading-Probleme ist die Verwendung von SynchronizationContext. Weitere Informationen finden Sie unter Synchronisieren von Aufrufen an die Benutzeroberfläche in einem Multithread-Anwendungsartikel .
quelle
Ein neuer Look mit Async / Await und Rückrufen. Sie benötigen nur eine Codezeile, wenn Sie die Erweiterungsmethode in Ihrem Projekt beibehalten.
Sie können der Erweiterungsmethode andere Dinge hinzufügen, z. B. das Umschließen in eine Try / Catch-Anweisung, damit der Aufrufer ihm mitteilen kann, welcher Typ nach Abschluss zurückgegeben werden soll. Dies ist ein Ausnahmerückruf an den Aufrufer:
Hinzufügen von Try Catch, Auto Exception Logging und CallBack
quelle
Dies ist nicht die empfohlene Methode, um diesen Fehler zu beheben. Sie können ihn jedoch schnell unterdrücken. Er erledigt die Aufgabe. Ich bevorzuge dies für Prototypen oder Demos. hinzufügen
im
Form1()
Konstruktor.quelle
Befolgen Sie die meiner Meinung nach einfachste Methode, um Objekte aus einem anderen Thread zu ändern:
quelle
Sie müssen sich das Backgroundworker-Beispiel ansehen:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx Insbesondere, wie es mit der UI-Ebene interagiert. Basierend auf Ihrem Beitrag scheint dies Ihre Probleme zu beantworten.
quelle
Ich habe dies beim Programmieren eines iOS-Phone-Monotouch-App-Controllers in einem Visual Studio-Winforms-Prototypprojekt außerhalb von xamarin stuidio festgestellt. Ich wollte lieber so viel wie möglich in VS über Xamarin Studio programmieren und wollte, dass der Controller vollständig vom Telefon-Framework entkoppelt wird. Auf diese Weise wäre die Implementierung für andere Frameworks wie Android und Windows Phone für zukünftige Anwendungen viel einfacher.
Ich wollte eine Lösung, bei der die GUI auf Ereignisse reagieren kann, ohne sich mit dem Cross-Threading-Schaltcode hinter jedem Tastenklick befassen zu müssen. Lassen Sie den Klassencontroller grundsätzlich damit umgehen, um den Clientcode einfach zu halten. Sie könnten möglicherweise viele Ereignisse auf der GUI haben, bei denen es sauberer wäre, wenn Sie es an einer Stelle in der Klasse handhaben könnten. Ich bin kein Multi-Theading-Experte. Lassen Sie mich wissen, wenn dies fehlerhaft ist.
Dem GUI-Formular ist nicht bekannt, dass der Controller asynchrone Aufgaben ausführt.
quelle
Hier ist eine alternative Möglichkeit, wenn das Objekt, mit dem Sie arbeiten, nicht vorhanden ist
Dies ist nützlich, wenn Sie mit dem Hauptformular in einer anderen Klasse als dem Hauptformular mit einem Objekt arbeiten, das sich im Hauptformular befindet, aber nicht über InvokeRequired verfügt
Es funktioniert genauso wie oben, aber es ist ein anderer Ansatz, wenn Sie kein Objekt mit invokerequired haben, aber Zugriff auf die MainForm haben
quelle
In Anlehnung an die vorherigen Antworten, jedoch eine sehr kurze Ergänzung, mit der alle Control-Eigenschaften verwendet werden können, ohne dass eine Ausnahme für den Aufruf eines Cross-Threads vorliegt.
Hilfsmethode
Beispielnutzung
quelle
quelle
Zum Beispiel, um den Text von einem Steuerelement des UI-Threads abzurufen:
quelle
Gleiche Frage: Wie-aktualisiere ich-die-GUI-von-einem-anderen-Thread-in-c
Zwei Wege:
Geben Sie den Wert in e.result zurück und verwenden Sie ihn, um Ihren Textfeldwert im Ereignis backgroundWorker_RunWorkerCompleted festzulegen
Deklarieren Sie eine Variable, um diese Art von Werten in einer separaten Klasse zu speichern (die als Datenhalter fungiert). Erstellen Sie eine statische Instanz dieser Klasse, und Sie können über einen beliebigen Thread darauf zugreifen.
Beispiel:
quelle
Verwenden Sie einfach dies:
quelle
Aktion y; // innerhalb der Klasse deklariert
label1.Invoke (y = () => label1.Text = "text");
quelle
Einfache und wiederverwendbare Möglichkeit, dieses Problem zu umgehen.
Erweiterungsmethode
Beispielnutzung
quelle
Es gibt zwei Optionen für Cross-Thread-Operationen.
und zweitens ist zu verwenden
Control.InvokeRequired ist nur nützlich, wenn Steuerelemente von der Control-Klasse geerbt werden, während SynchronizationContext überall verwendet werden kann. Einige nützliche Informationen sind folgende Links
Cross Thread Update UI | .Netz
Threadübergreifende Update-Benutzeroberfläche mit SynchronizationContext | .Netz
quelle