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

1217voto

Marc Gravell Punkte 970173

El einfachste Weg ist eine anonyme Methode, die an Label.Invoke :

// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
    // Running on the UI thread
    form.Label.Text = newText;
});
// Back on the worker thread

Beachten Sie, dass Invoke blockiert die Ausführung, bis sie abgeschlossen ist - das ist synchroner Code. In der Frage wird nicht nach asynchronem Code gefragt, aber es gibt eine Menge Inhalt auf Stack Overflow über das Schreiben von asynchronem Code, wenn Sie es lernen wollen.

2 Stimmen

Aber, dann muss Ihre Verarbeitungsfunktion eine Member-Methode Ihres GUI-Formulars sein?

8 Stimmen

Da der OP keine Klasse/Instanz erwähnt hat außer die Form, das ist kein schlechter Standard...

42 Stimmen

Vergessen Sie nicht, dass das Schlüsselwort "this" eine "Control"-Klasse referenziert.

824voto

Ian Kemp Punkte 26318

Für .NET 2.0 habe ich ein nettes Stück Code geschrieben, das genau das tut, was Sie wollen, und für jede Eigenschaft einer Control :

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

Sagen wir es so:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

Wenn Sie .NET 3.0 oder höher verwenden, können Sie die obige Methode als eine Erweiterungsmethode der Control Klasse, was dann den Aufruf zu vereinfachen würde:

myLabel.SetPropertyThreadSafe("Text", status);

UPDATE 05/10/2010:

Für .NET 3.0 sollten Sie diesen Code verwenden:

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

die LINQ und Lambda-Ausdrücke verwendet, um eine viel sauberere, einfachere und sicherere Syntax zu ermöglichen:

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

Jetzt wird nicht nur der Eigenschaftsname bei der Kompilierung geprüft, sondern auch der Typ der Eigenschaft, so dass es unmöglich ist, einer booleschen Eigenschaft einen String-Wert zuzuweisen und damit eine Laufzeitausnahme zu verursachen.

Leider hält das niemanden davon ab, Dummheiten zu begehen, wie z. B. einen anderen zu überholen. Control Eigenschaft und den Wert, so dass das Folgende problemlos kompiliert werden kann:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

Daher habe ich die Laufzeitprüfungen hinzugefügt, um sicherzustellen, dass die übergebene Eigenschaft tatsächlich zu der Control auf dem die Methode aufgerufen wird. Nicht perfekt, aber immer noch viel besser als die .NET 2.0 Version.

Wenn jemand weitere Vorschläge hat, wie man diesen Code für die Kompilierzeitsicherheit verbessern kann, bitte kommentieren!

3 Stimmen

Es gibt Fälle, in denen this.GetType() dasselbe Ergebnis wie propertyInfo.ReflectedType liefert (z. B. LinkLabel bei WinForms). Ich habe keine große C# Erfahrung, aber ich denke, dass die Bedingung für die Ausnahme sein sollte: if (propertyInfo == null || (!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) && @this.GetType() != propertyInfo.ReflectedType) || @this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)

9 Stimmen

@lan kann dies SetControlPropertyThreadSafe(myLabel, "Text", status) von einem anderen Modul oder einer Klasse oder einem Formular aufgerufen werden

0 Stimmen

Ich bin wieder dazu übergegangen, dies zu verwenden, weil ironischerweise BGWorker den UI-Thread einfriert Eigentlich bin ich mit der v2-Version zufrieden... danke....

468voto

Ryszard Dżegan Punkte 22810

Handhabung langer Arbeiten

Desde .NET 4.5 und C# 5.0 sollten Sie Aufgabenbasiertes asynchrones Muster (TAP) zusammen mit asynchron - warten Sie Schlüsselwörter in allen Bereichen (einschließlich der grafischen Benutzeroberfläche):

TAP ist das empfohlene asynchrone Entwurfsmuster für die Neuentwicklung

anstelle von Asynchrones Programmiermodell (APM) y Ereignisbasiertes asynchrones Muster (EAP) (letzteres umfasst die BackgroundWorker-Klasse ).

Die empfohlene Lösung für die Neuentwicklung lautet also:

  1. Asynchrone Implementierung eines Event-Handlers (Ja, das ist alles):

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var progress = new Progress<string>(s => label.Text = s);
        await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress),
                                    TaskCreationOptions.LongRunning);
        label.Text = "completed";
    }
  2. Implementierung des zweiten Threads, der den UI-Thread benachrichtigt:

    class SecondThreadConcern
    {
        public static void LongWork(IProgress<string> progress)
        {
            // Perform a long running work...
            for (var i = 0; i < 10; i++)
            {
                Task.Delay(500).Wait();
                progress.Report(i.ToString());
            }
        }
    }

Beachten Sie das Folgende:

  1. Kurzer und sauberer, sequentiell geschriebener Code ohne Callbacks und explizite Threads.
  2. Aufgabe anstelle von Thema .
  3. asynchron Schlüsselwort, das die Verwendung von warten Sie die wiederum verhindern, dass der Event-Handler den Abschlussstatus erreicht, bis die Aufgabe beendet ist und in der Zwischenzeit den UI-Thread nicht blockiert.
  4. Fortschrittsklasse (siehe IProgress Schnittstelle ), die Folgendes unterstützt Trennung der Belange (SoC) Designprinzip und erfordert keine expliziten Dispatcher und Aufrufe. Es verwendet die aktuelle Synchronisationskontext von seinem Erstellungsort (hier der UI-Thread).
  5. TaskCreationOptions.LongRunning die darauf hinweist, die Aufgabe nicht in die Warteschlange ThreadPool .

Ausführlichere Beispiele finden Sie unter: Die Zukunft von C#: Gutes kommt zu denen, die "warten von Joseph Albahari .

Siehe auch über UI-Threading-Modell Konzept.

Behandlung von Ausnahmen

Der folgende Ausschnitt ist ein Beispiel für die Behandlung von Ausnahmen und Umschalttasten Enabled um Mehrfachklicks während der Ausführung im Hintergrund zu verhindern.

private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}

2 Stimmen

Si SecondThreadConcern.LongWork() eine Ausnahme auslöst, kann diese vom UI-Thread abgefangen werden? Dies ist ein ausgezeichneter Beitrag, btw.

2 Stimmen

Ich habe der Antwort einen zusätzlichen Abschnitt hinzugefügt, um Ihre Anforderungen zu erfüllen. Mit freundlichen Grüßen.

3 Stimmen

En ExceptionDispatchInfo-Klasse ist verantwortlich für das Wunder der rethrowing Hintergrund Ausnahme auf UI-Thread in async-await Muster.

274voto

Zaid Masud Punkte 12759

Variation von Marc Gravell's einfachste solution für .NET 4:

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

Oder verwenden Sie stattdessen Action delegate:

control.Invoke(new Action(() => control.Text = "new text"));

Hier finden Sie einen Vergleich zwischen den beiden: MethodInvoker vs. Aktion für Control.BeginInvoke

1 Stimmen

Was bedeutet "Kontrolle" in diesem Beispiel? Mein UI-Steuerelement? Versuchen Sie, dies in WPF auf ein Label-Steuerelement zu implementieren, und Invoke ist kein Mitglied meines Labels.

0 Stimmen

Worum geht's Erweiterungsmethode wie @styxriver stackoverflow.com/a/3588137/206730 ?

0 Stimmen

Deklarieren Sie 'Action y;' innerhalb der Klasse oder Methode, die die Texteigenschaft ändert, und aktualisieren Sie den Text mit folgendem Code 'yourcontrol.Invoke(y=() => yourcontrol.Text = "neuer Text");'

154voto

StyxRiver Punkte 2135

Feuer und Vergessen Erweiterungsmethode für .NET 3.5+

using System;
using System.Windows.Forms;

public static class ControlExtensions
{
    /// <summary>
    /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
    /// </summary>
    /// <param name="control"></param>
    /// <param name="code"></param>
    public static void UIThread(this Control @this, Action code)
    {
        if (@this.InvokeRequired)
        {
            @this.BeginInvoke(code);
        }
        else
        {
            code.Invoke();
        }
    }
}

Dies kann mit der folgenden Code-Zeile aufgerufen werden:

this.UIThread(() => this.myLabel.Text = "Text Goes Here");

5 Stimmen

Was ist der Sinn der Verwendung von @this? Wäre "control" nicht gleichwertig? Gibt es irgendwelche Vorteile von @this?

16 Stimmen

@jeromeyers - Die @this ist einfach der Variablenname, in diesem Fall der Verweis auf das aktuelle Steuerelement, das die Erweiterung aufruft. Sie können sie in "source" umbenennen, oder was auch immer Sie wollen. 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" in normalem (nicht erweitertem) Code übereinstimmt.

2 Stimmen

Das ist großartig, einfach und für mich die beste Lösung. Man könnte die ganze Arbeit, die man zu tun hat, in den UI-Thread aufnehmen. Beispiel: this.UIThread(() => { txtMessage.Text = message; listBox1.Items.Add(message); });

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