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

9voto

Yuliia Ashomok Punkte 7783

Selbst wenn der Vorgang zeitaufwendig ist (thread.sleep in meinem Beispiel) - Dieser Code wird Ihre Benutzeroberfläche NICHT sperren:

 private void button1_Click(object sender, EventArgs e)
 {

      Thread t = new Thread(new ThreadStart(ThreadJob));
      t.IsBackground = true;
      t.Start();         
 }

 private void ThreadJob()
 {
     string newValue= "Hi";
     Thread.Sleep(2000); 

     this.Invoke((MethodInvoker)delegate
     {
         label1.Text = newValue; 
     });
 }

8voto

user53373 Punkte 91

Verwenden Sie einfach den Synchronisierungskontext der Benutzeroberfläche

using System.Threading;

// ...

public partial class MyForm : Form
{
    private readonly SynchronizationContext uiContext;

    public MyForm()
    {
        InitializeComponent();
        uiContext = SynchronizationContext.Current; // get ui thread context
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Thread t = new Thread(() =>
            {// set ui thread context to new thread context                            
             // for operations with ui elements to be performed in proper thread
             SynchronizationContext
                 .SetSynchronizationContext(uiContext);
             label1.Text = "some text";
            });
        t.Start();
    }
}

0 Stimmen

Aber sicher doch. Zu diesem Zweck habe ich Kommentare hinzugefügt.

0 Stimmen

Dies ist die einfachste und am besten lesbare Methode (IMO).

8voto

Sume Punkte 93

Ich habe gerade die Antworten gelesen und es scheint ein sehr heißes Thema zu sein. Ich bin derzeit mit .NET 3.5 SP1 und Windows Forms.

Die bekannte Formel, die in den vorangegangenen Antworten ausführlich beschrieben wurde und die sich auf die InvokeRequired Die Eigenschaft deckt die meisten Fälle ab, aber nicht den gesamten Pool.

Was wäre, wenn die Handgriff noch nicht erstellt worden ist?

El InvokeRequired Eigentum, wie beschrieben hier (Control.InvokeRequired-Eigenschaftsreferenz auf MSDN) gibt true zurück, wenn der Aufruf von einem Thread aus erfolgte, der nicht der GUI-Thread ist, false entweder, wenn der Aufruf vom GUI-Thread aus erfolgte, oder wenn die Handgriff wurde noch nicht erstellt.

Eine Ausnahme kann auftreten, wenn Sie ein modales Formular in einem anderen Thread anzeigen und aktualisieren lassen möchten. Da Sie möchten, dass das Formular modal angezeigt wird, könnten Sie Folgendes tun:

private MyForm _gui;

public void StartToDoThings()
{
    _gui = new MyForm();
    Thread thread = new Thread(SomeDelegate);
    thread.Start();
    _gui.ShowDialog();
}

Und der Delegierte kann ein Label auf der GUI aktualisieren:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.InvokeRequired)
        _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
    else
        _gui.Label1.Text = "Done!";
}

Dies kann zu einer InvalidOperationException wenn die Operationen vor der Aktualisierung des Etiketts "weniger Zeit in Anspruch nehmen" (lesen Sie es und interpretieren Sie es als Vereinfachung) als die Zeit, die der GUI-Thread benötigt, um die Formular 's Handgriff . Dies geschieht innerhalb der ShowDialog() Methode.

Sie sollten auch prüfen, ob die Handgriff wie diese:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.IsHandleCreated)  //  <---- ADDED
        if(_gui.InvokeRequired)
            _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
        else
            _gui.Label1.Text = "Done!";
}

Sie können die auszuführende Operation behandeln, wenn die Handgriff ist noch nicht erstellt worden: Sie können die GUI-Aktualisierung einfach ignorieren (wie im obigen Code gezeigt) oder Sie können warten (was riskanter ist). Damit sollte die Frage beantwortet sein.

Optionales Material: Persönlich habe ich mir das Folgende ausgedacht:

public class ThreadSafeGuiCommand
{
  private const int SLEEPING_STEP = 100;
  private readonly int _totalTimeout;
  private int _timeout;

  public ThreadSafeGuiCommand(int totalTimeout)
  {
    _totalTimeout = totalTimeout;
  }

  public void Execute(Form form, Action guiCommand)
  {
    _timeout = _totalTimeout;
    while (!form.IsHandleCreated)
    {
      if (_timeout <= 0) return;

      Thread.Sleep(SLEEPING_STEP);
      _timeout -= SLEEPING_STEP;
    }

    if (form.InvokeRequired)
      form.Invoke(guiCommand);
    else
      guiCommand();
  }
}

Ich füttere meine Formulare, die von einem anderen Thread aktualisiert werden, mit einer Instanz von diesem ThreadSafeGuiCommand , und ich definiere Methoden, die die GUI (in meinem Formular) wie folgt aktualisieren:

public void SetLabeTextTo(string value)
{
  _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}

Auf diese Weise bin ich ziemlich sicher, dass meine GUI aktualisiert wird, unabhängig davon, welcher Thread den Aufruf tätigt, wobei optional eine genau definierte Zeitspanne (der Timeout) abgewartet wird.

1 Stimmen

Ich bin hierher gekommen, um dies zu finden, da ich auch nach IsHandleCreated suche. Eine weitere zu prüfende Eigenschaft ist IsDisposed. Wenn Ihr Formular entsorgt ist, können Sie Invoke() nicht darauf aufrufen. Wenn der Benutzer das Formular geschlossen hat, bevor Ihr Hintergrund-Thread abgeschlossen werden konnte, möchten Sie nicht, dass er versucht, die Benutzeroberfläche aufzurufen, wenn das Formular entsorgt wurde.

0 Stimmen

Ich würde sagen, dass es eine schlechte Idee ist, damit anzufangen... Normalerweise würden Sie das untergeordnete Formular sofort anzeigen und einen Fortschrittsbalken oder eine andere Rückmeldung haben, während die Verarbeitung im Hintergrund läuft. Oder Sie würden die gesamte Verarbeitung zuerst durchführen und das Ergebnis dann bei der Erstellung an das neue Formular übergeben. Beides gleichzeitig zu tun, hätte in der Regel nur marginale Vorteile, aber viel weniger wartbaren Code.

0 Stimmen

Das beschriebene Szenario berücksichtigt eine modal Formular, das als Fortschrittsanzeige eines Hintergrund-Thread-Jobs verwendet wird. Da es modal sein muss, muss es durch den Aufruf der Funktion Form.ShowDialog() Methode. Auf diese Weise verhindern Sie, dass Ihr Code, der auf den Aufruf folgt, ausgeführt wird, bis das Formular geschlossen wird. Wenn Sie also den Hintergrund-Thread nicht anders als im angegebenen Beispiel starten können (und das können Sie natürlich), muss dieses Formular modal angezeigt werden, nachdem der Hintergrund-Thread gestartet wurde. In diesem Fall müssen Sie prüfen, ob der Handle erstellt werden muss. Wenn Sie kein modales Formular benötigen, ist das eine andere Geschichte.

8voto

Vasily Semenov Punkte 282

Ich denke, das ist der einfachste Weg:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }

7voto

MBH Punkte 15647

Ich konnte Microsofts Logik hinter dieser hässlichen Implementierung nicht nachvollziehen, aber man muss zwei Funktionen haben:

void setEnableLoginButton()
{
  if (InvokeRequired)
  {
    // btn_login can be any conroller, (label, button textbox ..etc.)

    btn_login.Invoke(new MethodInvoker(setEnable));

    // OR
    //Invoke(new MethodInvoker(setEnable));
  }
  else {
    setEnable();
  }
}

void setEnable()
{
  btn_login.Enabled = isLoginBtnEnabled;
}

Diese Schnipsel funktionieren bei mir, so dass ich etwas in einem anderen Thread tun kann, und dann aktualisiere ich die GUI:

Task.Factory.StartNew(()=>
{
    // THIS IS NOT GUI
    Thread.Sleep(5000);
    // HERE IS INVOKING GUI
    btn_login.Invoke(new Action(() => DoSomethingOnGUI()));
});

private void DoSomethingOnGUI()
{
   // GUI
   MessageBox.Show("message", "title", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}

Noch einfacher:

btn_login.Invoke(new Action(()=>{ /* HERE YOU ARE ON GUI */ }));

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