Ändern Sie WPF-Steuerelemente von einem Nicht-Haupt-Thread mit Dispatcher.Invoke


81

Ich habe vor kurzem mit der Programmierung in WPF begonnen und bin auf das folgende Problem gestoßen. Ich verstehe nicht, wie man die Dispatcher.Invoke()Methode benutzt. Ich habe Erfahrung im Threading und habe ein paar einfache Windows Forms-Programme erstellt, in denen ich gerade das verwendet habe

Control.CheckForIllegalCrossThreadCalls = false;

Ja, ich weiß, dass das ziemlich lahm ist, aber das waren einfache Überwachungsanwendungen.

Tatsache ist, dass ich jetzt eine WPF-Anwendung erstelle, die Daten im Hintergrund abruft. Ich starte einen neuen Thread, um den Aufruf zum Abrufen der Daten (von einem Webserver) durchzuführen. Jetzt möchte ich sie in meinem WPF-Formular anzeigen. Die Sache ist, ich kann keine Kontrolle über diesen Thread einstellen. Nicht einmal ein Etikett oder so. Wie kann das gelöst werden?

Antwortkommentare:
@Jalfp:
Also verwende ich diese Dispatcher-Methode im 'neuen Profil', wenn ich die Daten erhalte? Oder sollte ich einen Hintergrundmitarbeiter veranlassen, die Daten abzurufen, in ein Feld einzufügen und einen neuen Thread zu starten, der wartet, bis dieses Feld ausgefüllt ist, und den Dispatcher anrufen, um die abgerufenen Daten in den Steuerelementen anzuzeigen?


Das CheckForIllegalCrossThreadCalls ist fantastisch. Ich wünschte, ich wüsste das vorher für schnelle "wen interessiert das" -Anwendungen
Gaspa79

Antworten:


177

Das erste ist zu verstehen, dass der Dispatcher nicht für lange Blockierungsvorgänge ausgelegt ist (z. B. das Abrufen von Daten von einem WebServer ...). Sie können den Dispatcher verwenden, wenn Sie eine Operation ausführen möchten, die im UI-Thread ausgeführt wird (z. B. Aktualisieren des Werts eines Fortschrittsbalkens).

Sie können Ihre Daten in einem Hintergrund-Worker abrufen und mithilfe der ReportProgress-Methode Änderungen im UI-Thread weitergeben.

Wenn Sie den Dispatcher wirklich direkt verwenden müssen, ist dies ziemlich einfach:

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => this.progressBar.Value = 50));

22
Sie können den Teil 'new Action (' entfernen und einfach einen Lambda-Ausdruck verwenden: DispatcherPriority.Background, () => this.progressBar.Value = 50
jrista

Ja, ich weiß nicht, warum ich hier eine Aktion eingestellt habe: p
japf

1
@Carsten Diese Antwort gilt für WPF-Anwendungen, die die System.Windows.Application-Klasse verwenden.
Joshuapoehls

10
@ jrista: Kannst du wirklich? Ich bekomme CS1660, wenn ich es ohne versuche new Action(...).
ODER Mapper

6
@jrista: Im Allgemeinen wahr - obwohl dieser Artikel erklärt, warum es bei parameterlosen Methoden wie den an übergebenen nicht funktioniert BeginInvokeund stattdessen der Compilerfehler CS1660 ausgegeben wird.
ODER Mapper

31

japf hat es richtig beantwortet. Nur für den Fall, dass Sie mehrzeilige Aktionen betrachten, können Sie wie folgt schreiben.

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Background,
  new Action(() => { 
    this.progressBar.Value = 50;
  }));

Informationen für andere Benutzer, die mehr über die Leistung erfahren möchten:

Wenn Ihr Code für eine hohe Leistung geschrieben werden muss, können Sie zunächst mithilfe des CheckAccess-Flags überprüfen, ob der Aufruf erforderlich ist.

if(Application.Current.Dispatcher.CheckAccess())
{
    this.progressBar.Value = 50;
}
else
{
    Application.Current.Dispatcher.BeginInvoke(
      DispatcherPriority.Background,
      new Action(() => { 
        this.progressBar.Value = 50;
      }));
}

Beachten Sie, dass die Methode CheckAccess () in Visual Studio 2015 ausgeblendet ist. Schreiben Sie sie einfach, ohne zu erwarten, dass Intellisense sie anzeigt. Beachten Sie, dass CheckAccess einen Overhead für die Leistung hat (Overhead in wenigen Nanosekunden). Es ist nur dann besser, wenn Sie die Mikrosekunde sparen möchten, die erforderlich ist, um den Aufruf um jeden Preis durchzuführen. Es gibt auch immer die Möglichkeit, zwei Methoden zu erstellen (on mit Aufruf und andere ohne), wenn der Aufruf der Methode sicher ist, ob sie sich im UI-Thread befindet oder nicht. Es ist nur der seltenste Fall, wenn Sie sich diesen Aspekt des Dispatchers ansehen sollten.


4

Wenn ein Thread ausgeführt wird und Sie den Haupt-UI-Thread ausführen möchten, der vom aktuellen Thread blockiert wird, verwenden Sie Folgendes:

aktueller Thread:

Dispatcher.CurrentDispatcher.Invoke(MethodName,
    new object[] { parameter1, parameter2 }); // if passing 2 parameters to method.

Haupt-UI-Thread:

Application.Current.Dispatcher.BeginInvoke(
    DispatcherPriority.Background, new Action(() => MethodName(parameter)));

MethodName existiert im aktuellen Kontext nicht
AriesConnolly

0

Die obige @ japf-Antwort funktioniert einwandfrei und in meinem Fall wollte ich den Mauszeiger von einem sich drehenden Rad zurück auf den normalen Pfeil ändern, sobald der CEF-Browser das Laden der Seite beendet hat. Falls es jemandem helfen kann, hier ist der Code:

private void Browser_LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) {
   if (!e.IsLoading) {
      // set the cursor back to arrow
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
         new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
   }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.