Automatiser le modèle de code requis par InvokeRequired

je suis devenu douloureusement conscient de combien de fois il faut écrire le modèle de code suivant dans le code GUI event-driven, où

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

devient:

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

c'est un motif maladroit en C#, à la fois pour se souvenir, et pour taper. Quelqu'un a-t-il trouvé une sorte de raccourci ou de construction qui automatise cela jusqu'à un certain point? Ce serait cool s'il y avait un moyen d'attacher une fonction aux objets qui fait ce contrôle sans avoir à aller grâce à tout ce travail supplémentaire, comme un raccourci de type object1.InvokeIfNecessary.visible = true .

précédent les réponses ont discuté de l'impraticabilité de simplement appeler Invoke() à chaque fois, et même alors la syntaxe Invoke() est à la fois inefficace et encore difficile à traiter.

quelqu'un a-t-il trouvé un raccourci?

165
demandé sur Community 2010-03-03 02:29:10

9 réponses

L'approche de Lee peut être simplifiée davantage

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

et peut être appelé comme ceci

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

il n'est pas nécessaire de passer la commande comme paramètre au délégué. C # crée automatiquement une fermeture .


mise à jour :

selon plusieurs autres affiches Control peut être généralisée comme 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 a souligné que contrairement à Control l'interface ISynchronizeInvoke nécessite un tableau d'objets pour la méthode Invoke comme liste de paramètres pour action .


Maj 2

modifications suggérées par Mike De Klerk (voir le commentaire dans l'extrait de code 1 pour insérer le point):

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

voir Le commentaire de ToolmakerSteve ci-dessous pour les préoccupations au sujet de cette suggestion.

119
répondu Olivier Jacot-Descombes 2017-02-02 13:04:48

vous pouvez écrire une méthode d'extension:

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

et l'utiliser comme ceci:

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

EDIT: comme Simpzon le souligne dans les commentaires, vous pouvez aussi changer la signature en:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control
128
répondu Lee 2010-11-18 18:55:11

Voici le formulaire que j'ai utilisé dans tout mon code.

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

j'ai basé cela sur l'entrée de blog ici . Je n'ai pas eu cette approche me manquer, donc je ne vois aucune raison de compliquer mon code avec une vérification de la propriété InvokeRequired .

Espérons que cette aide.

33
répondu Matt Davis 2014-01-13 08:05:12

créer un ThreadSafeInvoke.fichier snippet, puis vous pouvez juste sélectionner les déclarations de mise à jour, clic droit et sélectionner " Surround With..."ou Ctrl-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>
9
répondu Aaron Gage 2011-02-15 13:42:56

Voici une version améliorée/combinée des réponses de Lee, Oliver et 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);
    }
} 

le modèle permet de code flexible et sans moulage qui est beaucoup plus lisible tandis que le délégué dédié fournit l'efficacité.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});
6
répondu gxtaillon 2015-04-07 17:50:04

je préférerais utiliser une seule instance D'un délégué de méthode au lieu de créer une nouvelle instance à chaque fois. Dans mon cas, j'avais l'habitude de montrer des messages de progrès et (info/error) d'un Backroundworker copiant et moulant de grandes données à partir d'une instance sql. Tout de suite après environ 70000 progrès et appels de message mon formulaire a cessé de fonctionner et de montrer de nouveaux messages. Cela ne s'est pas produit lorsque j'ai commencé à utiliser un seul délégué d'instance global.

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
répondu stephan Schmuck 2013-06-21 11:29:00

Utilisation:

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);
        }
    }
}
3
répondu Konstantin S. 2018-09-26 11:17:13

j'aime bien faire un peu différent, j'aime à les appeler "moi-même" si nécessaire à une Action", 151920920"

    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

c'est un motif pratique, L'IsFormClosing est un champ que je mets à True quand je ferme ma forme car il pourrait y avoir quelques fils de fond qui sont encore en cours d'exécution...

0
répondu Walter Verhoeven 2016-12-21 22:07:11

vous ne devriez jamais écrire un code qui ressemble à ceci:

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

si vous avez un code qui ressemble à cela, alors votre application n'est pas thread-safe. Cela signifie que vous avez du code qui appelle déjà DoGUISwitch() à partir d'un autre thread. C'est trop tard pour vérifier si c'est dans un autre fil. InvokeRequire doit être appelé avant de faire un appel à DoGUISwitch. Vous ne devriez pas accéder à n'importe quelle méthode ou propriété d'un fil différent.

Référence: Contrôle.InvokeRequired Property où vous pouvez lire ce qui suit:

en plus de la propriété InvokeRequired, il existe quatre méthodes un contrôle qui peut appeler en toute sécurité: Invoke, BeginInvoke, EndInvoke et CreateGraphics si la poignée pour le contrôle a déjà été créé.

dans une seule architecture CPU il n'y a pas de problème, mais dans un architecture multi-CPU vous pouvez faire en sorte qu'une partie du thread de L'interface utilisateur soit assignée au processeur où le code d'appel était en cours d'exécution...et si ce processeur est différent de l'endroit où le thread UI était en cours d'exécution, alors lorsque le thread appelant se termine, Windows pensera que le thread UI a pris fin et tuera le processus d'application, c'est-à-dire que votre application se terminera sans erreur.

-3
répondu Steve Wood 2014-10-29 13:10:30