Aufrufen (Delegieren)


91

Kann jemand bitte diese Aussage auf diesem Link erklären

Invoke(Delegate):

Führt den angegebenen Delegaten in dem Thread aus, dem das zugrunde liegende Fensterhandle des Steuerelements gehört.

Kann jemand erklären, was dies bedeutet (besonders die kühne)? Ich kann es nicht klar verstehen


4
Die Antwort auf diese Frage ist an die Control.InvokeRequired-Eigenschaft gebunden - siehe msdn.microsoft.com/en-us/library/…
Dash

Antworten:


129

Die Antwort auf diese Frage liegt in der Funktionsweise von C # Controls

Steuerelemente in Windows Forms sind an einen bestimmten Thread gebunden und nicht threadsicher. Wenn Sie die Methode eines Steuerelements von einem anderen Thread aus aufrufen, müssen Sie daher eine der Aufrufmethoden des Steuerelements verwenden, um den Aufruf für den richtigen Thread zu marshallen. Mit dieser Eigenschaft können Sie bestimmen, ob Sie eine Aufrufmethode aufrufen müssen. Dies kann hilfreich sein, wenn Sie nicht wissen, welcher Thread ein Steuerelement besitzt.

Von Control.InvokeRequired

Tatsächlich stellt Invoke sicher, dass der von Ihnen aufgerufene Code in dem Thread vorkommt, in dem das Steuerelement "lebt", und verhindert effektiv Thread-übergreifende Ausnahmen.

Aus historischer Sicht war dies in .Net 1.1 tatsächlich zulässig. Was es bedeutete, war, dass Sie versuchen konnten, Code auf dem "GUI" -Thread von jedem Hintergrund-Thread aus auszuführen, und dies würde meistens funktionieren. Manchmal wurde Ihre App einfach beendet, weil Sie den GUI-Thread effektiv unterbrochen haben, während er etwas anderes tat. Dies ist die Cross-Threaded-Ausnahme. Stellen Sie sich vor, Sie versuchen, eine TextBox zu aktualisieren, während die GUI etwas anderes malt.

  • Welche Aktion hat Priorität?
  • Ist es überhaupt möglich, dass beides gleichzeitig passiert?
  • Was passiert mit all den anderen Befehlen, die die GUI ausführen muss?

Tatsächlich unterbrechen Sie eine Warteschlange, was viele unvorhergesehene Folgen haben kann. Invoke ist praktisch die "höfliche" Methode, um das, was Sie tun möchten, in diese Warteschlange zu bringen. Diese Regel wurde ab .NET 2.0 über eine ausgelöste InvalidOperationException erzwungen .

Um zu verstehen, was sich tatsächlich hinter den Kulissen abspielt und was unter "GUI-Thread" zu verstehen ist, ist es hilfreich zu verstehen, was eine Nachrichtenpumpe oder eine Nachrichtenschleife ist.

Dies wird bereits in der Frage " Was ist eine Nachrichtenpumpe? " Beantwortet. Es wird empfohlen, diese zu lesen, um den tatsächlichen Mechanismus zu verstehen, an den Sie bei der Interaktion mit Steuerelementen gebunden sind.

Andere Lektüre, die Sie vielleicht nützlich finden, umfasst:

Was ist los mit Begin Invoke?

Eine der Grundregeln der Windows-GUI-Programmierung ist, dass nur der Thread, der ein Steuerelement erstellt hat, auf dessen Inhalt zugreifen und / oder ihn ändern kann (mit Ausnahme einiger dokumentierter Ausnahmen). Wenn Sie dies von einem anderen Thread aus tun, erhalten Sie ein unvorhersehbares Verhalten, das von Deadlocks über Ausnahmen bis hin zu einer halb aktualisierten Benutzeroberfläche reicht. Der richtige Weg, um ein Steuerelement von einem anderen Thread zu aktualisieren, besteht darin, eine entsprechende Nachricht an die Anwendungsnachrichtenwarteschlange zu senden. Wenn die Nachrichtenpumpe diese Nachricht ausführt, wird das Steuerelement auf demselben Thread aktualisiert, der sie erstellt hat (denken Sie daran, dass die Nachrichtenpumpe auf dem Hauptthread ausgeführt wird).

und für eine codeübergreifendere Übersicht mit einem repräsentativen Beispiel:

Ungültige Cross-Thread-Operationen

// the canonical form (C# consumer)

public delegate void ControlStringConsumer(Control control, string text);  // defines a delegate type

public void SetText(Control control, string text) {
    if (control.InvokeRequired) {
        control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text});  // invoking itself
    } else {
        control.Text=text;      // the "functional part", executing only on the main thread
    }
}

Sobald Sie eine Wertschätzung für InvokeRequired haben, können Sie eine Erweiterungsmethode zum Abschluss dieser Aufrufe in Betracht ziehen. Dies wird in der Frage zum Stapelüberlauf ausführlich behandelt . Bereinigen von Code, der mit Invoke Required übersät ist .

Es gibt auch eine weitere Beschreibung dessen, was historisch passiert ist und was von Interesse sein könnte.


67

Ein Steuerelement oder Fensterobjekt in Windows Forms ist nur ein Wrapper um ein Win32-Fenster, das durch ein Handle (manchmal auch als HWND bezeichnet) gekennzeichnet ist. Die meisten Dinge, die Sie mit dem Steuerelement tun, führen schließlich zu einem Win32-API-Aufruf, der dieses Handle verwendet. Das Handle gehört dem Thread, der es erstellt hat (normalerweise der Hauptthread) und sollte nicht von einem anderen Thread bearbeitet werden. Wenn Sie aus irgendeinem Grund etwas mit dem Steuerelement eines anderen Threads tun müssen, können Sie Invokeden Hauptthread bitten, dies in Ihrem Namen zu tun.

Wenn Sie beispielsweise den Text einer Beschriftung aus einem Arbeitsthread ändern möchten, können Sie Folgendes tun:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));

Können Sie erklären, warum jemand so etwas tun würde? this.Invoke(() => this.Enabled = true);Was auch immer sich darauf thisbezieht, ist sicherlich im aktuellen Thread, oder?
Kyle Delaney

1
@KyleDelaney, ein Objekt befindet sich nicht in einem Thread, und der aktuelle Thread ist nicht unbedingt der Thread, der das Objekt erstellt hat.
Thomas Levesque

24

Wenn Sie ein Steuerelement ändern möchten, muss dies in dem Thread erfolgen, in dem das Steuerelement erstellt wurde. Mit dieser InvokeMethode können Sie Methoden im zugeordneten Thread ausführen (dem Thread, dem das zugrunde liegende Fensterhandle des Steuerelements gehört).

Im folgenden Beispiel löst thread1 eine Ausnahme aus, da SetText1 versucht, textBox1.Text aus einem anderen Thread zu ändern. In Thread2 wird die Aktion in SetText2 jedoch in dem Thread ausgeführt, in dem die TextBox erstellt wurde

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}

Ich mag diesen Ansatz wirklich, er verbirgt die Natur der Delegierten, aber es ist trotzdem eine nette Abkürzung.
Shytikov

6
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

Die Verwendung von System.Action-Leuten, die für andere Antworten vorschlagen, funktioniert nur unter Framework 3.5+. Bei älteren Versionen funktioniert dies perfekt
Suicide Platypus

2

In der Praxis bedeutet dies, dass der Delegat garantiert im Hauptthread aufgerufen wird. Dies ist wichtig, da bei Windows-Steuerelementen die Änderung entweder nicht angezeigt wird oder das Steuerelement eine Ausnahme auslöst, wenn Sie deren Eigenschaften im Hauptthread nicht aktualisieren.

Das Muster ist:

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}

2

this.Invoke(delegate)Stellen Sie sicher, dass Sie den Delegaten aufrufen, an den das Argument this.Invoke()im Hauptthread / erstellten Thread gesendet wird.

Ich kann sagen, dass eine Thumb-Regel nur vom Haupt-Thread aus auf Ihre Formularsteuerelemente zugreift.

Möglicherweise sind die folgenden Zeilen für die Verwendung von Invoke () sinnvoll.

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

Es gibt Situationen, in denen Sie einen Threadpool-Thread (dh einen Arbeitsthread) erstellen, der auf dem Hauptthread ausgeführt wird. Es wird kein neuer Thread erstellt, da der Haupt-Thread für die Verarbeitung weiterer Anweisungen verfügbar ist. Untersuchen Sie zunächst, ob der aktuell ausgeführte Thread ein Hauptthread ist. Verwenden Sie dazu, this.InvokeRequiredwenn true zurückgegeben wird. Der aktuelle Code wird im Arbeitsthread ausgeführt. Rufen Sie also this.Invoke (d, new object [] {text}) auf.

Andernfalls aktualisieren Sie direkt das UI-Steuerelement (Hier wird garantiert, dass Sie den Code im Hauptthread ausführen.)


1

Dies bedeutet, dass der Delegat auf dem UI-Thread ausgeführt wird, selbst wenn Sie diese Methode von einem Hintergrund-Worker oder Thread-Pool-Thread aufrufen. UI-Elemente haben Thread-Affinität - sie sprechen nur gerne direkt mit einem Thread: dem UI-Thread. Der UI-Thread ist definiert als der Thread , der die Steuerinstanz erstellt hat, und ist daher dem Fensterhandle zugeordnet. All dies ist jedoch ein Implementierungsdetail.

Der entscheidende Punkt ist: Sie würden diese Methode von einem Arbeitsthread aus aufrufen, damit Sie auf die Benutzeroberfläche zugreifen können (um den Wert in einer Beschriftung usw. zu ändern), da Sie dies von keinem anderen Thread als dem Benutzeroberflächenthread aus tun dürfen .


0

Delegierte sind im Wesentlichen Inline Actionoder Func<T>. Sie können einen Delegaten außerhalb des Bereichs einer Methode deklarieren, die Sie ausführen oder die einen lambdaAusdruck ( =>) verwendet. Da Sie den Delegaten innerhalb einer Methode ausführen, führen Sie ihn auf dem Thread aus, der für das aktuelle Fenster / die aktuelle Anwendung ausgeführt wird.

Lambda Beispiel

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}

0

Dies bedeutet, dass der von Ihnen übergebene Delegat in dem Thread ausgeführt wird, der das Control-Objekt erstellt hat (dies ist der UI-Thread).

Sie müssen diese Methode aufrufen, wenn Ihre Anwendung über mehrere Threads verfügt und Sie eine UI-Operation von einem anderen Thread als dem UI-Thread ausführen möchten. Wenn Sie nur versuchen, eine Methode für ein Steuerelement von einem anderen Thread aus aufzurufen, erhalten Sie eine System.InvalidOperationException.

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.