web-dev-qa-db-fra.com

Automatisation du modèle de code InvokeRequired

Je suis devenu douloureusement conscient de la fréquence à laquelle il est nécessaire d'écrire le modèle de code suivant dans un code d'interface graphique événementielle, 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;
    }
}

Il s’agit d’un modèle peu pratique en C #, à la fois pour se rappeler et pour taper. Quelqu'un a-t-il proposé une sorte de raccourci ou de construction qui automatise cela dans une certaine mesure? Ce serait bien s'il y avait un moyen d'associer une fonction à des objets qui effectue cette vérification sans avoir à effectuer tout ce travail supplémentaire, comme un raccourci de type object1.InvokeIfNecessary.visible = true.

Previous réponses ont discuté de l’impraticabilité de simplement appeler Invoke () à chaque fois, et même dans ce cas, la syntaxe Invoke () est à la fois inefficace et toujours difficile à gérer.

Alors, est-ce que quelqu'un a trouvé des raccourcis?

175
Tom Corelis

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 s'appeler comme ça

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

Il n'est pas nécessaire de transmettre le contrôle en tant que paramètre au délégué. C # crée automatiquement un fermeture .


UPDATE:

Selon plusieurs autres affiches, Control peut être généralisé 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 en tant que liste de paramètres pour action.


UPDATE 2

Modifications suggérées par Mike de Klerk (voir le commentaire dans le premier extrait de code pour le point d'insertion):

// 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 concernant cette suggestion.

133

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 utilisez-le comme ceci:

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

EDIT: Comme Simpzon l’a souligné dans les commentaires, vous pouvez également modifier la signature en:

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

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é ceci sur l'entrée de blog ici . Comme cette approche ne m'a pas échoué, je ne vois donc aucune raison de compliquer mon code en vérifiant la propriété InvokeRequired.

J'espère que cela t'aides.

33
Matt Davis

Créez un fichier ThreadSafeInvoke.snippet, puis sélectionnez simplement les instructions de mise à jour, faites un clic droit et sélectionnez "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
Aaron Gage

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 un code souple et sans cast qui est beaucoup plus lisible, tandis que le délégué dédié est plus efficace.

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

Je préfère utiliser une seule instance d'une méthode Delegate au lieu de créer une nouvelle instance à chaque fois. Dans mon cas, j’avais l'habitude de montrer les progrès et les messages (info/erreur) d'un Backroundworker copiant et transmettant des données volumineuses à partir d'une instance SQL. Après environ 70000 progrès et appels de messages, mon formulaire a cessé de fonctionner et d'afficher de nouveaux messages. Cela ne s'est pas produit lorsque j'ai commencé à utiliser un seul délégué d'instance globale.

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);
}
4
stephan Schmuck

Usage:

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
Konstantin S.

J'aime bien le faire un peu différemment, j'aime m'appeler "moi-même" si nécessaire avec une action,

    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 modèle pratique, IsFormClosing est un champ que j'ai défini sur True lorsque je ferme mon formulaire car il se peut que des threads d'arrière-plan soient toujours en cours d'exécution ...

2
Walter Verhoeven