Automatisieren des InvokeRequired-Codemusters


179

Mir ist schmerzlich bewusst geworden, wie oft man das folgende Codemuster in ereignisgesteuerten GUI-Code schreiben muss, wo

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

wird:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Dies ist ein umständliches Muster in C #, sowohl zum Erinnern als auch zum Tippen. Hat jemand eine Verknüpfung oder ein Konstrukt entwickelt, das dies bis zu einem gewissen Grad automatisiert? Es wäre cool, wenn es eine Möglichkeit gäbe, Objekten, die diese Prüfung durchführen, eine Funktion zuzuweisen, ohne all diese zusätzlichen Arbeiten wie eine object1.InvokeIfNecessary.visible = trueTypverknüpfung ausführen zu müssen.

In früheren Antworten wurde die Unpraktikabilität diskutiert, jedes Mal nur Invoke () aufzurufen, und selbst dann ist die Invoke () -Syntax sowohl ineffizient als auch immer noch umständlich zu handhaben.

Hat jemand irgendwelche Abkürzungen herausgefunden?


2
Ich habe mich das Gleiche gefragt, aber in Bezug auf WPFs Dispatcher.CheckAccess ().
Taylor Leese

Ich habe mir einen ziemlich verrückten Vorschlag ausgedacht, der von Ihrer object1.InvokeIfNecessary.Visible = trueLinie inspiriert ist. Schauen Sie sich meine aktualisierte Antwort an und teilen Sie mir Ihre Meinung mit.
Dan Tao

1
Fügen Sie ein Snippet hinzu, um die von Matt Davis vorgeschlagene Methode zu implementieren: siehe meine Antwort (spät, zeigt aber nur, wie es für spätere Leser ist ;-))
Aaron Gage

3
Ich verstehe nicht, warum Microsoft in .NET nichts unternommen hat, um dies zu vereinfachen. Das Erstellen von Delegaten für jede Änderung des Formulars aus dem Thread ist wirklich ärgerlich.
Kamil

@Kamil Ich könnte nicht mehr zustimmen! Dies ist angesichts seiner Allgegenwart ein solches Versehen. Behandeln Sie innerhalb des Frameworks bei Bedarf einfach das Einfädeln. Scheint offensichtlich.
SteveCinq

Antworten:


138

Lees Ansatz kann weiter vereinfacht werden

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

Und kann so genannt werden

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

Es ist nicht erforderlich, das Steuerelement als Parameter an den Delegaten zu übergeben. C # erstellt automatisch einen Abschluss .


UPDATE :

Nach mehreren anderen Postern Controlkann verallgemeinert werden als ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott wies darauf hin, dass im Gegensatz Controlzur ISynchronizeInvokeSchnittstelle ein Objektarray für die InvokeMethode als Parameterliste für die erforderlich ist action.


UPDATE 2

Von Mike de Klerk vorgeschlagene Änderungen (Einfügungspunkt siehe Kommentar im 1. Codeausschnitt):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

Weitere Informationen zu diesem Vorschlag finden Sie im Kommentar von ToolmakerSteve.


2
Wäre es nicht besser, haben ISynchronizeInvokestatt Control? (Ein großes
Lob

@odyodyodys: Guter Punkt. Ich wusste es nicht ISynchronizeInvoke. Aber der einzige Typ, der sich daraus ableitet (laut Reflector), ist Control, so dass der Vorteil begrenzt ist.
Olivier Jacot-Descombes

3
@ Mike-de-Clerk, ich bin besorgt über Ihren Vorschlag hinzuzufügen while (!control.Visible) ..sleep... Für mich hat das einen schlechten Code-Geruch, da es sich um eine möglicherweise unbegrenzte Verzögerung handelt (in einigen Fällen sogar um eine Endlosschleife), in Code, der möglicherweise Anrufer enthält, die keine solche Verzögerung (oder sogar einen Deadlock) erwarten. IMHO, jede Verwendung von Sleepsollte in der Verantwortung jedes Anrufers liegen, ODER sollte in einem separaten Wrapper sein, der klar in Bezug auf seine Konsequenzen gekennzeichnet ist. IMHO, normalerweise ist es besser, "hart zu versagen" (Ausnahme, während des Testens zu fangen) oder "nichts zu tun", wenn die Steuerung nicht bereit ist. Bemerkungen?
ToolmakerSteve

1
@ OlivierJacot-Descombes, Es wäre toll, wenn Sie bitte erklären, wie thread.invokerequired dahinter funktioniert?
Sudhir.net

1
InvokeRequiredGibt an, ob sich der aufrufende Thread von dem Thread unterscheidet, der das Steuerelement erstellt hat. InvokeÜbergibt die Aktion vom aufrufenden Thread an den Thread des Steuerelements, in dem sie ausgeführt wird. Dies stellt sicher, dass beispielsweise ein Klickereignishandler niemals unterbrochen wird.
Olivier Jacot-Descombes

133

Sie könnten eine Erweiterungsmethode schreiben:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

Und benutze es so:

object1.InvokeIfRequired(c => { c.Visible = true; });

BEARBEITEN: Wie Simpzon in den Kommentaren hervorhebt, können Sie die Signatur auch ändern in:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

Vielleicht bin ich einfach zu dumm, aber dieser Code wird nicht kompiliert. Also habe ich es so repariert, wie es von mir gebaut wurde (VS2008).
Oliver

5
Nur der Vollständigkeit halber: In WPF gibt es einen anderen Versandmechanismus, der jedoch ziemlich analog funktioniert. Sie können diese Erweiterungsmethode dort verwenden: public static void InvokeIfRequired <T> (dieses T aTarget, Aktion <T> aActionToExecute) wobei T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
Ich habe eine Antwort hinzugefügt, die Lees Lösung leicht vereinfacht.
Olivier Jacot-Descombes

Hallo, da ich etwas Ähnliches verwende, kann es ein großes Problem bei dieser generischen Implementierung geben. Wenn das Steuerelement Disposing / Disposed ist, erhalten Sie eine ObjectDisposedException.
Offler

1
@Offler - Nun, wenn sie auf einem anderen Thread angeordnet sind, haben Sie ein Synchronisationsproblem, es ist kein Problem bei dieser Methode.
Lee

33

Hier ist das Formular, das ich in meinem gesamten Code verwendet habe.

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

Ich habe dies auf dem Blogeintrag hier basiert . Ich habe diesen Ansatz nicht verfehlt, daher sehe ich keinen Grund, meinen Code mit einer Überprüfung der zu komplizierenInvokeRequired Eigenschaft .

Hoffe das hilft.


+1 - Ich bin auf den gleichen Blogeintrag gestoßen, den Sie gemacht haben, und denke, dies ist der sauberste Ansatz aller vorgeschlagenen
Tom Bushell

3
Bei diesem Ansatz gibt es einen kleinen Leistungseinbruch, der sich bei mehrmaligem Aufruf häufen kann. stackoverflow.com/a/747218/724944
surfen

4
Sie müssen verwenden, InvokeRequiredwenn der Code ausgeführt werden konnte, bevor das Steuerelement angezeigt wurde, oder Sie haben eine schwerwiegende Ausnahme.
56ka

9

Erstellen Sie eine ThreadSafeInvoke.snippet-Datei, und wählen Sie dann einfach die Update-Anweisungen aus, klicken Sie mit der rechten Maustaste und wählen Sie "Surround With ..." oder Strg-K + S:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>

6

Hier ist eine verbesserte / kombinierte Version der Antworten von Lee, Oliver und Stephan.

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

Die Vorlage ermöglicht flexiblen und Cast-freien Code, der viel besser lesbar ist, während der dedizierte Delegat für Effizienz sorgt.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});

4

Ich würde lieber eine einzelne Instanz einer Methode Delegate verwenden, anstatt jedes Mal eine neue Instanz zu erstellen. In meinem Fall habe ich den Fortschritt und (Info- / Fehler-) Meldungen eines Backroundworkers angezeigt, die große Datenmengen von einer SQL-Instanz kopierten und umwandelten. Nach ungefähr 70000 Fortschritten und Nachrichtenaufrufen funktionierte mein Formular nicht mehr und zeigte neue Nachrichten an. Dies trat nicht auf, als ich anfing, einen einzelnen globalen Instanzdelegaten zu verwenden.

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}

3

Verwendung:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

Code:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}

2

Ich mache es gerne ein bisschen anders, ich nenne es gerne "mich", wenn es nötig ist, mit einer Aktion,

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

Dies ist ein praktisches Muster. Das IsFormClosing ist ein Feld, das ich beim Schließen meines Formulars auf True gesetzt habe, da möglicherweise noch einige Hintergrundthreads ausgeführt werden ...


-3

Sie sollten niemals Code schreiben, der so aussieht:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Wenn Sie Code haben, der so aussieht, ist Ihre Anwendung nicht threadsicher. Dies bedeutet, dass Sie Code haben, der DoGUISwitch () bereits von einem anderen Thread aus aufruft. Es ist zu spät, um zu überprüfen, ob es sich um einen anderen Thread handelt. InvokeRequire muss aufgerufen werden, BEVOR Sie DoGUISwitch anrufen. Sie sollten nicht von einem anderen Thread aus auf eine Methode oder Eigenschaft zugreifen.

Referenz: Control.InvokeRequired-Eigenschaft, in der Sie Folgendes lesen können:

Zusätzlich zur InvokeRequired-Eigenschaft gibt es vier Methoden für ein Steuerelement, die threadsicher aufgerufen werden können: Invoke, BeginInvoke, EndInvoke und CreateGraphics, wenn das Handle für das Steuerelement bereits erstellt wurde.

In einer einzelnen CPU-Architektur gibt es kein Problem, aber in einer Architektur mit mehreren CPUs kann ein Teil des UI-Threads dem Prozessor zugewiesen werden, auf dem der aufrufende Code ausgeführt wurde ... und wenn sich dieser Prozessor von dem des UI-Threads unterscheidet wurde dann ausgeführt, wenn der aufrufende Thread beendet wird Windows wird denken, dass der UI-Thread beendet wurde und den Anwendungsprozess beenden, dh Ihre Anwendung wird ohne Fehler beendet.


Hey, danke für deine Antwort. Es ist Jahre her, seit ich diese Frage gestellt habe (und fast genauso lange, seit ich mit C # gearbeitet habe), aber ich habe mich gefragt, ob Sie etwas weiter erklären könnten? Die Dokumente, die Sie verlinkt haben, verweisen auf eine bestimmte Gefahr des Aufrufs von invoke()et al., Bevor das Steuerelement ein Handle erhält, aber IMHO beschreibt nicht, was Sie beschrieben haben. Der springende Punkt bei all diesem invoke()Unsinn ist, die Benutzeroberfläche threadsicher zu aktualisieren, und ich würde denken, dass das Platzieren weiterer Anweisungen in einem blockierenden Kontext zu Stottern führen würde. (Ugh ... froh, dass ich aufgehört habe, M $ tech zu verwenden. So kompliziert!)
Tom Corelis

Ich möchte auch darauf hinweisen, dass ich trotz häufiger Verwendung des Originalcodes (vor
langer

3
Ich bezweifle, dass diese Antwort richtig ist, da MSDN viele Beispiele zeigt, genau wie das OP.
öffentliche drahtlose
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.