1592 Stimmen

Wie aktualisiere ich die GUI von einem anderen Thread aus?

Welches ist der einfachste Weg, eine Label von einem anderen Thread ?

  • Tengo un Form weiterlaufend thread1 und davon ausgehend eröffne ich ein weiteres Thema ( thread2 ).

  • Während thread2 einige Dateien verarbeitet, würde ich gerne eine Label sur le Form mit dem aktuellen Status von thread2 Arbeit.

Wie könnte ich das tun?

28 Stimmen

Hat .net 2.0+ nicht die BackgroundWorker-Klasse genau für diesen Zweck. Es UI Thread bewusst. 1. Erstellen Sie einen BackgroundWorker 2. Fügen Sie zwei Delegierte hinzu (einen für die Verarbeitung und einen für den Abschluss)

15 Stimmen

4 Stimmen

Siehe die Antwort für .NET 4.5 und C# 5.0: stackoverflow.com/a/18033198/2042090

34voto

Brian Gideon Punkte 46450

Wegen der Trivialität des Szenarios würde ich den UI-Thread tatsächlich nach dem Status fragen lassen. Ich denke, Sie werden feststellen, dass es ziemlich elegant sein kann.

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

Dieser Ansatz vermeidet die bei der Verwendung der Methode ISynchronizeInvoke.Invoke y ISynchronizeInvoke.BeginInvoke Methoden. Gegen die Verwendung der Marshaling-Technik ist nichts einzuwenden, aber es gibt eine Reihe von Vorbehalten, die Sie beachten sollten.

  • Stellen Sie sicher, dass Sie nicht anrufen BeginInvoke zu häufig, sonst könnte die Nachrichtenpumpe überlastet werden.
  • Aufruf von Invoke auf dem Worker-Thread ist ein blockierender Aufruf. Er hält die Arbeit in diesem Thread vorübergehend an.

Die Strategie, die ich in dieser Antwort vorschlage, kehrt die Kommunikationsrollen der Threads um. Anstatt dass der Worker-Thread die Daten schiebt, fragt der UI-Thread sie ab. Dies ist ein gängiges Muster, das in vielen Szenarien verwendet wird. Da Sie nur Fortschrittsinformationen aus dem Worker-Thread anzeigen möchten, denke ich, dass diese Lösung eine gute Alternative zur Marshaling-Lösung ist. Sie hat die folgenden Vorteile.

  • Die UI- und Worker-Threads bleiben lose gekoppelt, im Gegensatz zu den Control.Invoke o Control.BeginInvoke Ansatz, der sie eng miteinander verbindet.
  • Der UI-Thread wird den Fortschritt des Worker-Threads nicht behindern.
  • Der Worker-Thread kann nicht die Zeit dominieren, die der UI-Thread mit der Aktualisierung verbringt.
  • Die Intervalle, in denen die UI- und Worker-Threads Operationen durchführen, können unabhängig voneinander bleiben.
  • Der Worker-Thread kann die Nachrichtenpumpe des UI-Threads nicht überlasten.
  • Der UI-Thread bestimmt, wann und wie oft die Benutzeroberfläche aktualisiert wird.

3 Stimmen

Eine gute Idee. Das einzige, was Sie nicht erwähnt haben, ist, wie Sie den Timer ordnungsgemäß entsorgen, sobald der WorkerThread beendet ist. Beachten Sie, dass dies zu Problemen führen kann, wenn die Anwendung endet (d. h. der Benutzer schließt die Anwendung). Haben Sie eine Idee, wie man dieses Problem lösen kann?

0 Stimmen

@Matt Anstatt einen anonymen Handler für Elapsed Ereignis, verwenden Sie eine Member-Methode, damit Sie den Timer entfernen können, wenn das Formular entsorgt wird...

0 Stimmen

@Phil1970 - Gutes Argument. Sie meinten wie System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; }; und deren Zuweisung über m_Timer.Elapsed += handler; , später im Dispose-Kontext mit einer m_Timer.Elapsed -= handler; Habe ich Recht? Und für die Entsorgung/Schließung folgen Sie den besprochenen Ratschlägen aquí .

32voto

Kieron Punkte 25804

Sie müssen die Methode im GUI-Thread aufrufen. Das können Sie tun, indem Sie Control.Invoke aufrufen.

Zum Beispiel:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}

1 Stimmen

Die Invoke-Zeile gibt einen Compiler-Fehler aus. Die beste überladene Methode für 'System.Windows.Forms.Control.Invoke(System.Delegate, object[])' hat einige ungültige Argumente

30voto

Jon H Punkte 301

Das Invoke-Zeug aus den vorherigen Antworten ist nicht notwendig.

Sie müssen sich WindowsFormsSynchronizationContext ansehen:

// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();

...

// In some non-UI Thread

// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);

...

void UpdateGUI(object userData)
{
    // Update your GUI controls here
}

5 Stimmen

Was glauben Sie, was die Post-Methode unter der Haube verwendet? :)

26voto

Francis Punkte 299

Diese Lösung ähnelt der obigen Lösung mit .NET Framework 3.0, aber sie löst das Problem der Unterstützung der Sicherheit bei der Kompilierung .

public  static class ControlExtension
{
    delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);

    public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
    {
        if (source.InvokeRequired)
        {
            var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
            source.Invoke(del, new object[]{ source, selector, value});
        }
        else
        {
            var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
            propInfo.SetValue(source, value, null);
        }
    }
}

Zu verwenden:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

Der Compiler schlägt fehl, wenn der Benutzer den falschen Datentyp übergibt.

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");

26voto

bgmCoder Punkte 6037

Salvete! Nachdem ich nach dieser Frage gesucht habe, fand ich die Antworten bei FrankG y Der Geist von Oregon die für mich am einfachsten und nützlichsten sind. Ich programmiere in Visual Basic und habe dieses Snippet durch einen Konverter laufen lassen, daher bin ich mir nicht ganz sicher, wie es ausgeht.

Ich habe ein Dialogformular namens form_Diagnostics, die ein Feld für den Rechtstext enthält, das updateDiagWindow, die ich als eine Art Logging-Display verwende. Ich musste in der Lage sein, den Text aus allen Threads zu aktualisieren. Die zusätzlichen Zeilen ermöglichen es dem Fenster, automatisch zu den neuesten Zeilen zu blättern.

Und so kann ich jetzt die Anzeige mit einer Zeile aktualisieren, von überall im gesamten Programm, so wie Sie denken, dass es ohne Threading funktionieren würde:

  form_Diagnostics.updateDiagWindow(whatmessage);

Hauptcode (innerhalb des Klassencodes Ihres Formulars):

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X