Wie aktualisiere ich die GUI von einem anderen Thread?


1392

Welches ist der einfachste Weg, um ein Labelanderes zu aktualisieren Thread?

  • Ich habe einen FormLauf auf thread1und von dort starte ich einen anderen Thread ( thread2).

  • Während der thread2Verarbeitung einiger Dateien möchte ich eine Labelauf dem Formmit dem aktuellen Status der thread2Arbeit aktualisieren .

Wie könnte ich das machen?


25
Verfügt .net 2.0+ nicht nur dafür über die BackgroundWorker-Klasse? 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 die Fertigstellung)
Preet Sangha


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

5
Diese Frage gilt nicht für Gtk # GUI. Für Gtk # siehe dies und diese Antwort.
Hlovdal

Achtung: Die Antworten auf diese Frage sind jetzt ein Durcheinander von OT ("Hier ist, was ich für meine WPF-App getan habe") und historischen .NET 2.0-Artefakten.
Marc L.

Antworten:


768

Für .NET 2.0 ist hier ein netter Code, den ich geschrieben habe, der genau das tut, was Sie wollen, und der für jede Eigenschaft auf einem funktioniert 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 });
  }
}

Nennen Sie 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 Erweiterungsmethode der ControlKlasse umschreiben , wodurch der Aufruf von:

myLabel.SetPropertyThreadSafe("Text", status);

UPDATE 10.05.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

Der Eigenschaftsname wird jetzt nicht nur zur Kompilierungszeit überprüft, sondern auch der Typ der Eigenschaft. Daher ist es unmöglich, einer booleschen Eigenschaft (beispielsweise) einen Zeichenfolgenwert zuzuweisen und damit eine Laufzeitausnahme auszulösen.

Leider hindert dies niemanden daran, dumme Dinge zu tun, wie z. B. das ControlEigentum und den Wert eines anderen weiterzugeben. Daher wird Folgendes gerne kompiliert:

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

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

Wenn jemand weitere Vorschläge zur Verbesserung dieses Codes für die Sicherheit beim Kompilieren hat, kommentieren Sie dies bitte!


3
Es gibt Fälle, in denen this.GetType () dasselbe wie propertyInfo.ReflectedType (z. B. LinkLabel unter WinForms) ergibt. 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)
Corvin

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

71
Die bereitgestellte Lösung ist unnötig komplex. Sehen Sie sich die Lösung von Marc Gravell oder Zaid Masud an, wenn Sie Wert auf Einfachheit legen.
Frank Hileman

8
Diese Lösung verschwendet Tonnen von Ressourcen, wenn Sie mehrere Eigenschaften aktualisieren, da jeder Aufruf viele Ressourcen kostet. Ich glaube nicht, dass das Feature von Thread Safety sowieso so gedacht war. Verkapseln Sie Ihre UI-Aktualisierungsaktionen und rufen Sie sie EINMAL (und nicht pro Eigenschaft) auf
Konsole

4
Warum um alles in der Welt würden Sie diesen Code über die BackgroundWorker-Komponente verwenden?
Andy

1080

Der einfachste Weg ist eine anonyme Methode, die übergeben wird 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 Invokedie Ausführung blockiert wird, bis sie abgeschlossen ist - dies ist synchroner Code. Die Frage bezieht sich nicht auf asynchronen Code, aber es gibt viele Inhalte zu Stack Overflow über das Schreiben von asynchronem Code, wenn Sie mehr darüber erfahren möchten.


8
Da das OP keine Klasse / Instanz außer dem Formular erwähnt hat, ist dies kein schlechter Standard ...
Marc Gravell

39
Vergessen Sie nicht, dass das Schlüsselwort "this" auf eine "Control" -Klasse verweist.
AZ.

8
@codecompleting ist in beiden Fällen sicher und wir wissen bereits, dass wir einen Arbeiter haben. Warum also etwas überprüfen, das wir wissen?
Marc Gravell

4
@Dragouf nicht wirklich - einer der Punkte bei der Verwendung dieser Methode ist, dass Sie bereits wissen, welche Teile auf dem Worker und welche auf dem UI-Thread ausgeführt werden. Keine Notwendigkeit zu überprüfen.
Marc Gravell

3
@ Joan.bdm Es gibt bei weitem nicht genug Kontext, um dies zu kommentieren
Marc Gravell

401

Lange Arbeit erledigen

Seit .NET 4.5 und C # 5.0 sollten Sie das aufgabenbasierte asynchrone Muster (TAP) zusammen mit asynchronem verwenden - warten Sie auf Schlüsselwörter in allen Bereichen (einschließlich der GUI):

TAP ist das empfohlene asynchrone Entwurfsmuster für Neuentwicklungen

anstelle des asynchronen Programmiermodells (APM) und des ereignisbasierten asynchronen Musters (EAP) (letzteres umfasst die BackgroundWorker-Klasse ).

Dann ist die empfohlene Lösung für die Neuentwicklung:

  1. Asynchrone Implementierung eines Ereignishandlers (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 Folgendes:

  1. Kurzer und sauberer Code, sequentiell geschrieben, ohne Rückrufe und explizite Threads.
  2. Aufgabe statt Thread .
  3. asynchrones Schlüsselwort, das die Verwendung von wait ermöglicht, wodurch wiederum verhindert wird, dass der Ereignishandler den Abschlussstatus erreicht, bis die Aufgabe abgeschlossen ist, und in der Zwischenzeit den UI-Thread nicht blockiert.
  4. Fortschrittsklasse (siehe IProgress-Schnittstelle ), die das SoC- Entwurfsprinzip (Separation of Concerns ) unterstützt und kein explizites Dispatchern und Aufrufen erfordert. Es verwendet den aktuellen SynchronizationContext von seinem Erstellungsort (hier der UI-Thread).
  5. TaskCreationOptions.LongRunning , das angibt , die Aufgabe nicht in ThreadPool in die Warteschlange zu stellen .

Finden Sie eine ausführlichere Beispiele: Die Zukunft von C #: Gute Sachen kommen zu denen , die ‚await‘ von Joseph Albahari .

Siehe auch zum UI-Threading-Modellkonzept .

Ausnahmen behandeln

Das folgende Snippet ist ein Beispiel für die Behandlung von Ausnahmen und das Umschalten der Schaltflächeneigenschaften Enabled, um mehrere Klicks während der Hintergrundausführung 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
Wenn SecondThreadConcern.LongWork()eine Ausnahme ausgelöst wird, kann sie vom UI-Thread abgefangen werden? Dies ist übrigens ein ausgezeichneter Beitrag.
Kdbanman

2
Ich habe der Antwort einen zusätzlichen Abschnitt hinzugefügt, um Ihre Anforderungen zu erfüllen. Grüße.
Ryszard Dżegan

3
Die ExceptionDispatchInfo-Klasse ist für das Wunder verantwortlich, dass die Hintergrundausnahme im UI-Thread im asynchronen Wartemuster erneut ausgelöst wird.
Ryszard Dżegan

1
Ist es nur ich, der denkt, dass diese Art und Weise viel ausführlicher ist, als nur Invoke / Begin aufzurufen?!
MeTitus

2
Task.Delay(500).Wait()? Was bringt es, eine Aufgabe zu erstellen, um nur den aktuellen Thread zu blockieren? Sie sollten niemals einen Thread-Pool-Thread blockieren!
Yarik

236

Variation von Marc Gravells einfachster Lösung für .NET 4:

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

Oder verwenden Sie stattdessen den Aktionsdelegierten:

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

Hier finden Sie einen Vergleich der beiden: MethodInvoker vs Action for Control.BeginInvoke


1
Was ist in diesem Beispiel "Kontrolle"? Meine UI-Steuerung? Der Versuch, dies in WPF auf einem Label-Steuerelement zu implementieren, und Invoke ist kein Mitglied meines Labels.
Dbloom

Was ist mit der Erweiterungsmethode wie @styxriver stackoverflow.com/a/3588137/206730 ?
Kiquenet

deklariere 'Aktion y;' innerhalb der Klasse oder Methode die Texteigenschaft ändern und den Text mit diesem Code aktualisieren 'yourcontrol.Invoke (y = () => yourcontrol.Text = "neuer Text");'
Antonio Leite

4
@Dbloom ist kein Mitglied, da es nur für WinForms ist. Für WPF verwenden Sie Dispatcher.Invoke
sLw

1
Ich habe diese Lösung verfolgt, aber manchmal wurde meine Benutzeroberfläche nicht aktualisiert. Ich fand, dass ich die this.refresh()Ungültigmachung erzwingen und die GUI neu streichen muss .. wenn es hilfreich ist ..
Rakibul Haq

137

Feuern und vergessen Sie die 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 Codezeile aufgerufen werden:

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

5
Was ist der Sinn dieser Verwendung? Wäre "Kontrolle" nicht gleichwertig? Gibt es irgendwelche Vorteile für @this?
Argyle

14
@jeromeyers - Dies @thisist einfach der Variablenname, in diesem Fall der Verweis auf das aktuelle Steuerelement, das die Erweiterung aufruft. Sie können es in Quelle umbenennen oder was auch immer Ihr Boot schwimmt. 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" im normalen Code (ohne Erweiterung) übereinstimmt.
StyxRiver

1
Das ist großartig, einfach und für mich die beste Lösung. Sie können alle Arbeiten, die Sie erledigen müssen, in den UI-Thread aufnehmen. Beispiel: this.UIThread (() => {txtMessage.Text = message; listBox1.Items.Add (message);});
Auto

1
Diese Lösung gefällt mir sehr gut. Minor nit: Ich würde diese Methode OnUIThreadeher nennen als UIThread.
ToolmakerSteve

2
Deshalb habe ich diese Erweiterung benannt RunOnUiThread. Aber das ist nur persönlicher Geschmack.
Grisgram

66

Dies ist der klassische Weg, wie Sie dies tun sollten:

using System;
using System.Windows.Forms;
using System.Threading;

namespace Test
{
    public partial class UIThread : Form
    {
        Worker worker;

        Thread workerThread;

        public UIThread()
        {
            InitializeComponent();

            worker = new Worker();
            worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
            workerThread = new Thread(new ThreadStart(worker.StartWork));
            workerThread.Start();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
        {
            // Cross thread - so you don't get the cross-threading exception
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    OnWorkerProgressChanged(sender, e);
                });
                return;
            }

            // Change control
            this.label1.Text = e.Progress;
        }
    }

    public class Worker
    {
        public event EventHandler<ProgressChangedArgs> ProgressChanged;

        protected void OnProgressChanged(ProgressChangedArgs e)
        {
            if(ProgressChanged!=null)
            {
                ProgressChanged(this,e);
            }
        }

        public void StartWork()
        {
            Thread.Sleep(100);
            OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
            Thread.Sleep(100);
        }
    }


    public class ProgressChangedArgs : EventArgs
    {
        public string Progress {get;private set;}
        public ProgressChangedArgs(string progress)
        {
            Progress = progress;
        }
    }
}

Ihr Arbeitsthread hat ein Ereignis. Ihr UI-Thread startet einen anderen Thread, um die Arbeit zu erledigen, und verbindet dieses Worker-Ereignis, damit Sie den Status des Worker-Threads anzeigen können.

Dann müssen Sie in der Benutzeroberfläche Threads kreuzen, um das eigentliche Steuerelement zu ändern ... wie eine Beschriftung oder einen Fortschrittsbalken.


62

Die einfache Lösung ist zu verwenden Control.Invoke.

void DoSomething()
{
    if (InvokeRequired) {
        Invoke(new MethodInvoker(updateGUI));
    } else {
        // Do Something
        updateGUI();
    }
}

void updateGUI() {
    // update gui here
}

Gut gemacht für die Einfachheit! nicht nur einfach, sondern funktioniert auch gut! Ich habe wirklich nicht verstanden, warum Microsoft es nicht einfacher machen kann, wie es sein soll! Um 1 Zeile im Haupt-Thread aufzurufen, sollten wir einige Funktionen schreiben!
MBH

1
@ MBH zustimmen. Übrigens, haben Sie die Antwort von stackoverflow.com/a/3588137/199364 oben bemerkt , die eine Erweiterungsmethode definiert? Tun Sie das einmal in einer benutzerdefinierten Dienstprogrammklasse, dann müssen Sie sich nicht mehr darum kümmern, dass Microsoft es nicht für uns getan hat :)
ToolmakerSteve

@ToolmakerSteve Genau das bedeutete es zu sein! Sie haben Recht, wir können einen Weg finden, aber ich meine, aus der Sicht von DRY (wiederholen Sie sich nicht) kann das Problem, das eine gemeinsame Lösung hat, von ihnen mit minimalem Aufwand von Microsoft gelöst werden, was viel Zeit spart Programmierer :)
MBH

47

Threading-Code ist oft fehlerhaft und immer schwer zu testen. Sie müssen keinen Threading-Code schreiben, um die Benutzeroberfläche von einer Hintergrundaufgabe aus zu aktualisieren. Verwenden Sie einfach die BackgroundWorker- Klasse, um die Aufgabe auszuführen , und die ReportProgress- Methode, um die Benutzeroberfläche zu aktualisieren. Normalerweise melden Sie nur einen Prozentsatz als abgeschlossen, aber es gibt eine weitere Überladung, die ein Statusobjekt enthält. Hier ist ein Beispiel, das nur ein Zeichenfolgenobjekt meldet:

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "A");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "B");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "C");
    }

    private void backgroundWorker1_ProgressChanged(
        object sender, 
        ProgressChangedEventArgs e)
    {
        label1.Text = e.UserState.ToString();
    }

Das ist in Ordnung, wenn Sie immer dasselbe Feld aktualisieren möchten. Wenn Sie kompliziertere Aktualisierungen vornehmen müssen, können Sie eine Klasse definieren, die den UI-Status darstellt, und diese an die ReportProgress-Methode übergeben.

Als letztes müssen Sie das WorkerReportsProgressFlag setzen, ReportProgresssonst wird die Methode vollständig ignoriert.


2
Am Ende der Verarbeitung ist es auch möglich, die Benutzeroberfläche über zu aktualisieren backgroundWorker1_RunWorkerCompleted.
DavidRR

41

Die überwiegende Mehrheit der Antworten verwendet, Control.Invokewas eine Rennbedingung ist, die darauf wartet, passiert zu werden . Betrachten Sie zum Beispiel die akzeptierte Antwort:

string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate { 
    someLabel.Text = newText; // runs on UI thread
});

Wenn der Benutzer das Formular kurz vor dem this.InvokeAufruf schließt (denken Sie daran, thisist das FormObjekt), ObjectDisposedExceptionwird wahrscheinlich ein ausgelöst.

Die Lösung ist die Verwendung SynchronizationContext, insbesondere SynchronizationContext.Currentwie von hamilton.danielb vorgeschlagen (andere Antworten beruhen auf bestimmten SynchronizationContextImplementierungen, was völlig unnötig ist). Ich würde seinen Code leicht modifizieren, um ihn zu verwenden, SynchronizationContext.Postanstatt ihn zu verwenden SynchronizationContext.Send(da der Arbeitsthread normalerweise nicht warten muss):

public partial class MyForm : Form
{
    private readonly SynchronizationContext _context;
    public MyForm()
    {
        _context = SynchronizationContext.Current
        ...
    }

    private MethodOnOtherThread()
    {
         ...
         _context.Post(status => someLabel.Text = newText,null);
    }
}

Beachten Sie, dass Sie unter .NET 4.0 und höher wirklich Aufgaben für asynchrone Vorgänge verwenden sollten. In der Antwort von n-san finden Sie den entsprechenden aufgabenbasierten Ansatz (unter Verwendung TaskScheduler.FromCurrentSynchronizationContext).

Schließlich können Sie unter .NET 4.5 und höher auch Progress<T>(was im Wesentlichen SynchronizationContext.Currentbei seiner Erstellung erfasst wird) verwenden, wie von Ryszard Dżegan's für Fälle demonstriert, in denen der lang laufende Vorgang UI-Code ausführen muss, während er noch arbeitet.


37

Sie müssen sicherstellen, dass das Update auf dem richtigen Thread erfolgt. der UI-Thread.

Dazu müssen Sie den Event-Handler aufrufen, anstatt ihn direkt aufzurufen.

Sie können dies tun, indem Sie Ihre Veranstaltung wie folgt auslösen:

(Der Code wird hier aus meinem Kopf heraus eingegeben, daher habe ich nicht nach korrekter Syntax usw. gesucht, aber er sollte Sie zum Laufen bringen.)

if( MyEvent != null )
{
   Delegate[] eventHandlers = MyEvent.GetInvocationList();

   foreach( Delegate d in eventHandlers )
   {
      // Check whether the target of the delegate implements 
      // ISynchronizeInvoke (Winforms controls do), and see
      // if a context-switch is required.
      ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;

      if( target != null && target.InvokeRequired )
      {
         target.Invoke (d, ... );
      }
      else
      {
          d.DynamicInvoke ( ... );
      }
   }
}

Beachten Sie, dass der obige Code in WPF-Projekten nicht funktioniert, da WPF-Steuerelemente die ISynchronizeInvokeSchnittstelle nicht implementieren .

Um sicher zu stellen , dass der Code über Arbeiten mit Windows Forms und WPF, und alle anderen Plattformen, Sie einen Blick auf die haben kann AsyncOperation, AsyncOperationManagerund SynchronizationContextKlassen.

Um Ereignisse auf diese Weise einfach auszulösen, habe ich eine Erweiterungsmethode erstellt, mit der ich das Auslösen eines Ereignisses vereinfachen kann, indem ich einfach Folgendes aufrufe:

MyEvent.Raise(this, EventArgs.Empty);

Natürlich können Sie auch die BackGroundWorker-Klasse verwenden, die diese Angelegenheit für Sie abstrahiert.


In der Tat, aber ich mag es nicht, meinen GUI-Code mit dieser Angelegenheit zu überladen. Meiner GUI sollte es egal sein, ob sie aufgerufen werden muss oder nicht. Mit anderen Worten: Ich glaube nicht, dass es die Verantwortung der GUI ist, den Kontextwechsel durchzuführen.
Frederik Gheysels

1
Das Auseinanderbrechen des Delegaten usw. scheint übertrieben - warum nicht einfach: SynchronizationContext.Current.Send (Delegat {MyEvent (...);}, null);
Marc Gravell

Haben Sie immer Zugriff auf den SynchronizationContext? Auch wenn Ihre Klasse in einer Klassenbibliothek ist?
Frederik Gheysels

29

Sie müssen die Methode im GUI-Thread aufrufen. Sie können dies 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
Die Aufrufzeile gibt mir einen Compilerfehler. Die beste überladene Methodenübereinstimmung für 'System.Windows.Forms.Control.Invoke (System.Delegate, object [])' hat einige ungültige Argumente
CruelIO

28

Aufgrund der Trivialität des Szenarios hätte ich tatsächlich die UI-Thread-Abfrage für den Status. Ich denke, Sie werden feststellen, dass es sehr 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...";
    }
  }
}

Der Ansatz vermeidet den Marshalling-Vorgang, der bei Verwendung der Methoden ISynchronizeInvoke.Invokeund erforderlich ist ISynchronizeInvoke.BeginInvoke. Es ist nichts Falsches daran, die Marshalling-Technik zu verwenden, aber es gibt ein paar Vorsichtsmaßnahmen, die Sie beachten müssen.

  • Stellen Sie sicher, dass Sie nicht BeginInvokezu häufig anrufen, da sonst die Nachrichtenpumpe überlaufen könnte.
  • Das Aufrufen Invokedes Worker-Threads ist ein blockierender Aufruf. Die in diesem Thread ausgeführte Arbeit wird vorübergehend angehalten.

Die Strategie, die ich in dieser Antwort vorschlage, kehrt die Kommunikationsrollen der Threads um. Anstatt dass der Worker-Thread die Daten pusht, fragt der UI-Thread danach. Dies ist ein häufiges Muster, das in vielen Szenarien verwendet wird. Da Sie lediglich Fortschrittsinformationen aus dem Worker-Thread anzeigen möchten, werden Sie feststellen, dass diese Lösung eine hervorragende Alternative zur Marshalling-Lösung darstellt. Es hat die folgenden Vorteile.

  • Die UI und Arbeitsthreads bleiben locker , im Gegensatz zu dem gekoppelten Control.Invokeoder Control.BeginInvokeAnsatz sie die fest Paare.
  • Der UI-Thread behindert den Fortschritt des Worker-Threads nicht.
  • Der Arbeitsthread kann die Zeit, die der UI-Thread für die Aktualisierung benötigt, nicht dominieren.
  • Die Intervalle, in denen die Benutzeroberfläche und die Arbeitsthreads Vorgänge ausführen, können unabhängig bleiben.
  • Der Worker-Thread kann die Nachrichtenpumpe des UI-Threads nicht überlaufen.
  • Der UI-Thread bestimmt, wann und wie oft die UI aktualisiert wird.

3
Gute Idee. Das einzige, was Sie nicht erwähnt haben, ist, wie Sie den Timer nach Beendigung des WorkerThread ordnungsgemäß entsorgen. Beachten Sie, dass dies zu Problemen führen kann, wenn die Anwendung beendet wird (dh der Benutzer schließt die Anwendung). Haben Sie eine Idee, wie Sie das lösen können?
Matt

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

@ Phil1970 - Guter Punkt. Du hast es gemocht System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };und zugewiesen über m_Timer.Elapsed += handler;, später im Entsorgungskontext mache m_Timer.Elapsed -= handler;ich ein Recht? Und für die Entsorgung / Schließung folgen Sie den hier besprochenen Ratschlägen .
Matt

27

Keines der Invoke-Elemente in den vorherigen Antworten ist erforderlich.

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
}

4
Was verwendet die Post-Methode Ihrer Meinung nach unter der Haube? :)
ungläubig

23

Diese Lösung ähnelt der obigen Lösung mit .NET Framework 3.0, hat jedoch das Problem der Sicherheitsunterstützung zur Kompilierungszeit gelöst .

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);
        }
    }
}

Benutzen:

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");

23

Salvete! Nachdem ich nach dieser Frage gesucht hatte, fand ich die Antworten von FrankG und Oregon Ghost am einfachsten und nützlichsten für mich. Jetzt habe ich in Visual Basic codiert und dieses Snippet durch einen Konverter ausgeführt. Ich bin mir also nicht ganz sicher, wie es ausgeht.

Ich habe ein Dialogformular mit dem Namen form_Diagnostics,richtext, updateDiagWindow,das ich als eine Art Protokollierungsanzeige verwende. Ich musste in der Lage sein, seinen Text aus allen Threads zu aktualisieren. Durch die zusätzlichen Zeilen kann das Fenster automatisch zu den neuesten Zeilen scrollen.

Und so kann ich jetzt die Anzeige mit einer Zeile von überall im gesamten Programm auf die Weise aktualisieren, von der Sie glauben, dass sie ohne Threading funktionieren würde:

  form_Diagnostics.updateDiagWindow(whatmessage);

Hauptcode (fügen Sie diesen in den Klassencode Ihres Formulars ein):

#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

21

Für viele Zwecke ist es so einfach:

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

"serviceGUI ()" ist eine Methode auf GUI-Ebene innerhalb des Formulars (this), mit der beliebig viele Steuerelemente geändert werden können. Rufen Sie "updateGUI ()" aus dem anderen Thread auf. Parameter können hinzugefügt werden, um Werte zu übergeben, oder (wahrscheinlich schneller) nach Bedarf Klassenbereichsvariablen mit Sperren verwenden, wenn die Möglichkeit eines Konflikts zwischen Threads besteht, die auf sie zugreifen und Instabilität verursachen können. Verwenden Sie BeginInvoke anstelle von Invoke, wenn der Nicht-GUI-Thread zeitkritisch ist (unter Berücksichtigung der Warnung von Brian Gideon).


21

Dies in meiner C # 3.0-Variante von Ian Kemps Lösung:

public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}

Sie nennen es so:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. Es fügt dem Ergebnis von "as MemberExpression" eine Nullprüfung hinzu.
  2. Es verbessert die statische Typensicherheit.

Ansonsten ist das Original eine sehr schöne Lösung.


21
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

Beachten Sie, dass dies BeginInvoke()vorgezogen wird, Invoke()da es weniger wahrscheinlich zu Deadlocks kommt (dies ist hier jedoch kein Problem, wenn Sie nur einem Etikett Text zuweisen):

Bei der Verwendung Invoke()warten Sie auf die Rückkehr der Methode. Nun kann es sein, dass Sie im aufgerufenen Code etwas tun, das auf den Thread warten muss, was möglicherweise nicht sofort offensichtlich ist, wenn er in einigen von Ihnen aufgerufenen Funktionen vergraben ist, was selbst indirekt über Ereignishandler geschehen kann. Sie würden also auf den Thread warten, der Thread würde auf Sie warten und Sie sind festgefahren.

Dies führte tatsächlich dazu, dass einige unserer veröffentlichten Software hängen blieben. Es war leicht genug , um fix durch den Ersatz Invoke()mit BeginInvoke(). Verwenden Sie die Option, es sei denn, Sie benötigen einen Synchronbetrieb. Dies kann der Fall sein, wenn Sie einen Rückgabewert benötigen BeginInvoke().


20

Als ich auf dasselbe Problem stieß, suchte ich Hilfe bei Google, aber anstatt mir eine einfache Lösung zu geben, verwirrte es mich mehr, indem ich Beispiele für MethodInvokerund bla bla bla gab. Also habe ich beschlossen, es selbst zu lösen. Hier ist meine Lösung:

Machen Sie einen Delegierten wie folgt:

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

Sie können diese Funktion in einem neuen Thread wie diesem aufrufen

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

Sei nicht verwechselt mit Thread(() => .....). Ich verwende eine anonyme Funktion oder einen Lambda-Ausdruck, wenn ich an einem Thread arbeite. Um die Codezeilen zu reduzieren, können Sie auch die ThreadStart(..)Methode verwenden, die ich hier nicht erklären soll.


17

Verwenden Sie einfach so etwas:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });

Wenn e.ProgressPercentageja, befinden Sie sich nicht bereits im UI-Thread der Methode, die Sie aufrufen?
LarsTech

Das ProgressChanged-Ereignis wird auf dem UI-Thread ausgeführt. Dies ist einer der Vorteile der Verwendung des BackgroundWorker. Das abgeschlossene Ereignis wird auch auf der GUI ausgeführt. Das einzige, was im Nicht-UI-Thread ausgeführt wird, ist die DoWork-Methode.
LarsTech

15

Sie können den bereits vorhandenen Delegaten verwenden Action:

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}

14

Meine Version besteht darin, eine Zeile des rekursiven "Mantras" einzufügen :

Für keine Argumente:

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

Für eine Funktion mit Argumenten:

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

DAS IST ES .


Einige Argumente : Normalerweise ist es für die Lesbarkeit des Codes schlecht, {} nach einer if ()Anweisung in eine Zeile zu setzen. Aber in diesem Fall ist es Routine "Mantra". Die Lesbarkeit des Codes wird nicht beeinträchtigt, wenn diese Methode über das Projekt hinweg konsistent ist. Und es schützt Ihren Code vor Müll (eine Codezeile statt fünf).

Wie Sie sehen if(InvokeRequired) {something long}, wissen Sie nur "diese Funktion kann sicher von einem anderen Thread aufgerufen werden".


13

Versuchen Sie, das Etikett damit zu aktualisieren

public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}

Ist es für Windows Forms ?
Kiquenet

13

Erstellen Sie eine Klassenvariable:

SynchronizationContext _context;

Legen Sie es im Konstruktor fest, der Ihre Benutzeroberfläche erstellt:

var _context = SynchronizationContext.Current;

Wenn Sie das Etikett aktualisieren möchten:

_context.Send(status =>{
    // UPDATE LABEL
}, null);

12

Sie müssen invoke und delegate verwenden

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });

12

Die meisten anderen Antworten sind für mich in dieser Frage etwas komplex (ich bin neu in C #), daher schreibe ich meine:

Ich habe eine WPF- Anwendung und habe einen Worker wie folgt definiert:

Problem:

BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}

Lösung:

workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}

Ich muss noch herausfinden, was die obige Zeile bedeutet, aber es funktioniert.

Für WinForms :

Lösung:

txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});

Die Frage betraf Winforms, nicht WPF.
Marc L.

Vielen Dank. WinForms-Lösung oben hinzugefügt.
Manohar Reddy Poreddy

... das ist nur eine Kopie von wie vielen anderen Antworten auf dieselbe Frage, aber okay. Warum nicht Teil der Lösung sein und einfach Ihre Antwort löschen?
Marc L.

hmm, richtig, wenn Sie nur meine Antwort mit Aufmerksamkeit lesen, den Anfangsteil (der Grund, warum ich die Antwort geschrieben habe), und hoffentlich mit etwas mehr Aufmerksamkeit sehen Sie, dass es jemanden gibt, der genau das gleiche Problem hatte und für den Sie heute gestimmt haben Meine einfache Antwort, und mit noch mehr Aufmerksamkeit, wenn Sie die wahre Geschichte darüber vorhersehen könnten, warum dies alles passiert ist, schickt mich Google hierher, selbst wenn ich nach wpf suche. Sicher, da Sie diese mehr oder weniger offensichtlichen 3 Gründe verpasst haben, kann ich verstehen, warum Sie Ihre Ablehnung nicht entfernen werden. Erstellen Sie etwas Neues, das viel schwieriger ist, anstatt das Okay zu reinigen.
Manohar Reddy Poreddy

8

Der einfachste Weg, den ich denke:

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

8

Greifen Sie beispielsweise auf ein anderes Steuerelement als im aktuellen Thread zu:

Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));

Dort lblThresholdist das ein Label und Speed_Thresholdeine globale Variable.


8

Wenn Sie sich im UI-Thread befinden, können Sie ihn nach seinem Taskplaner für den Synchronisationskontext fragen. Sie erhalten einen TaskScheduler , der alles im UI-Thread plant.

Anschließend können Sie Ihre Aufgaben so verketten, dass eine andere Aufgabe (die im UI-Thread geplant ist) sie auswählt und einem Label zuweist, wenn das Ergebnis fertig ist.

public partial class MyForm : Form
{
  private readonly TaskScheduler _uiTaskScheduler;
  public MyForm()
  {
    InitializeComponent();
    _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
  {
    RunAsyncOperation();
  }

  private void RunAsyncOperation()
  {
    var task = new Task<string>(LengthyComputation);
    task.ContinueWith(antecedent =>
                         UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
    task.Start();
  }

  private string LengthyComputation()
  {
    Thread.Sleep(3000);
    return "47";
  }

  private void UpdateResultLabel(string text)
  {
    labelResult.Text = text;
  }
}

Dies funktioniert für Aufgaben (nicht für Threads), die derzeit die bevorzugte Methode zum Schreiben von gleichzeitigem Code sind .



8

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

Die in den vorherigen Antworten ausführlich beschriebene bekannte Formel, die die InvokeRequired- Eigenschaft verwendet, deckt die meisten Fälle ab, jedoch nicht den gesamten Pool.

Was ist, wenn der Griff noch nicht erstellt wurde?

Die InvokeRequired- Eigenschaft, wie hier beschrieben (Control.InvokeRequired-Eigenschaftsreferenz auf MSDN), gibt true zurück, wenn der Aufruf von einem Thread aus erfolgt, der nicht der GUI-Thread ist, false, entweder wenn der Aufruf vom GUI-Thread erfolgt ist oder wenn das Handle ausgeführt wurde noch nicht erstellt.

Sie können auf eine Ausnahme stoßen, wenn ein modales Formular von einem anderen Thread angezeigt und aktualisiert werden soll. Da dieses Formular modal angezeigt werden soll, können Sie Folgendes tun:

private MyForm _gui;

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

Und der Delegat 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 eine verursachen InvalidOperationException , wenn die Vorgänge vor dem Update des Labels „weniger Zeit in Anspruch nehmen“ (lesen Sie es und es als eine Vereinfachung interpretieren) als die Zeit, die für den GUI - Thread nimmt die erstellen Formular ‚s Griff . Dies geschieht innerhalb der ShowDialog () -Methode.

Sie sollten auch wie folgt nach dem Griff suchen :

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 den auszuführenden Vorgang ausführen, wenn das Handle noch nicht erstellt wurde: Sie können das GUI-Update einfach ignorieren (wie im obigen Code gezeigt) oder warten (riskanter). Dies sollte die Frage beantworten.

Optionales Material: Persönlich habe ich Folgendes programmiert:

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 dieses ThreadSafeGuiCommand und 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 mir ziemlich sicher, dass ich meine GUI aktualisieren werde, unabhängig davon, welcher Thread den Anruf tätigt, und optional auf eine genau definierte Zeitspanne (das Timeout) warten wird.


1
Kam hierher, um dies zu finden, da ich auch nach IsHandleCreated gesucht habe. Eine weitere zu überprü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 zurückzurufen, wenn das Formular entsorgt wird.
Jon

Ich würde sagen, dass es eine schlechte Idee ist, mit ... zu beginnen. Normalerweise würden Sie das untergeordnete Formular sofort anzeigen und während der Hintergrundverarbeitung einen Fortschrittsbalken oder ein anderes Feedback erhalten. Oder Sie führen zuerst die gesamte Verarbeitung durch und übergeben das Ergebnis dann bei der Erstellung an das neue Formular. Beides gleichzeitig zu tun hätte im Allgemeinen marginale Vorteile, aber viel weniger wartbaren Code.
Phil1970

Das beschriebene Szenario berücksichtigt eine modale Form, die als Fortschrittsansicht eines Hintergrund-Thread-Jobs verwendet wird. Da es modal sein muss, muss es durch Aufrufen der Form.ShowDialog () -Methode angezeigt werden. Auf diese Weise verhindern Sie, dass Ihr Code, der auf den Aufruf folgt, ausgeführt wird, bis das Formular geschlossen wird. Sofern Sie den Hintergrundthread nicht anders als im angegebenen Beispiel starten können (und dies natürlich auch können), muss dieses Formular nach dem Start des Hintergrundthreads modal angezeigt werden. In diesem Fall müssen Sie überprüfen, ob das Handle erstellt werden soll. Wenn Sie keine modale Form benötigen, ist es eine andere Geschichte.
Sume
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.