Automatisation du modèle de code InvokeRequired


179

Je suis devenu douloureusement conscient de la fréquence à laquelle il faut écrire le modèle de code suivant dans le code GUI événementiel, 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 modèle gênant en C #, à la fois à retenir et à taper. Quelqu'un a-t-il trouvé une sorte de raccourci ou de construction qui automatise cela dans une certaine mesure? Ce serait cool s'il y avait un moyen d'attacher une fonction aux objets qui effectue cette vérification sans avoir à passer par tout ce travail supplémentaire, comme un object1.InvokeIfNecessary.visible = trueraccourci de type.

Les réponses précédentes ont discuté de l'impossibilité 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?


2
Je me suis demandé la même chose, mais en ce qui concerne Dispatcher.CheckAccess () de WPF.
Taylor Leese

J'ai imaginé une suggestion assez folle inspirée de votre object1.InvokeIfNecessary.Visible = trueligne; consultez ma réponse mise à jour et dites-moi ce que vous en pensez.
Dan Tao

1
Ajouter un extrait pour aider à implémenter la méthode suggérée par Matt Davis: voir ma réponse (en retard mais juste en montrant comment pour les lecteurs ultérieurs ;-))
Aaron Gage

3
Je ne comprends pas pourquoi Microsoft n'a rien fait pour simplifier cela dans .NET. Créer des délégués pour chaque changement sur le formulaire à partir du thread est vraiment ennuyeux.
Kamil

@Kamil je ne pourrais pas être plus d'accord! C'est un tel oubli, étant donné son omniprésence. Dans le cadre, il suffit de gérer le filetage si nécessaire. Cela semble évident.
SteveCinq

Réponses:


138

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

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 ç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 .


MISE À JOUR :

Selon plusieurs autres affiches Controlpeuvent être généralisées 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 à Controll' ISynchronizeInvokeinterface, il faut un tableau d'objets pour la Invokeméthode comme liste de paramètres pour leaction .


MISE À JOUR 2

Modifications suggérées par Mike de Klerk (voir le commentaire dans le 1er 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.


2
Ne serait-il pas préférable d'avoir ISynchronizeInvokeau lieu de Control? (Félicitations à Jon Skeet stackoverflow.com/questions/711408/… )
Odys

@odyodyodys: Bon point. Je ne savais pas ISynchronizeInvoke. Mais le seul type qui en dérive (selon Reflector) est Control, donc l'avantage est limité.
Olivier Jacot-Descombes

3
@ mike-de-clerk, je suis préoccupé par votre suggestion d'ajouter while (!control.Visible) ..sleep... Pour moi, cela a une mauvaise odeur de code, car il s'agit d'un délai potentiellement illimité (peut-être même une boucle infinie dans certains cas), dans un code qui peut avoir des appelants qui ne s'attendent pas à un tel retard (ou même à une impasse). À mon humble avis, toute utilisation de Sleepdevrait être la responsabilité de chaque appelant, OU devrait être dans un emballage séparé clairement indiqué quant à ses conséquences. À mon humble avis, il serait généralement préférable de "échouer dur" (exception, pour attraper pendant les tests), ou de "ne rien faire" si le contrôle n'est pas prêt. Commentaires?
ToolmakerSteve

1
@ OlivierJacot-Descombes, Ce serait génial, si vous expliquez comment thread.invokerequired fonctionne derrière?
Sudhir.net

1
InvokeRequiredindique si le thread appelant est différent du thread qui a créé le contrôle. Invoketransmet l'action du thread appelant au thread du contrôle où elle est exécutée. Cela garantit que, par exemple, un gestionnaire d'événements Click n'est jamais interrompu.
Olivier Jacot-Descombes

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 le souligne dans les commentaires, vous pouvez également changer la signature en:

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

Peut-être que je suis trop stupide, mais ce code ne se compilera pas. Je l'ai donc réparé tel qu'il a été construit par moi (VS2008).
Oliver

5
Juste pour être complet: dans WPF, il existe un mécanisme de distribution différent, mais il fonctionne plutôt de manière analogue. Vous pouvez utiliser cette méthode d'extension ici: public static void InvokeIfRequired <T> (this T aTarget, Action <T> aActionToExecute) où T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
J'ai ajouté une réponse qui simplifie légèrement la solution de Lee.
Olivier Jacot-Descombes

Salut, comme j'utilisais quelque chose de similaire, il peut y avoir un gros problème provenant de cette implémentation générique. Si le contrôle est en cours d'élimination / d'élimination, vous obtiendrez une ObjectDisposedException.
Offler

1
@Offler - Eh bien, s'ils sont supprimés sur un thread différent, vous avez un problème de synchronisation, ce n'est pas un problème dans cette méthode.
Lee

33

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 . Je n'ai pas vu cette approche échouer, donc je ne vois aucune raison de compliquer mon code avec une vérification duInvokeRequired propriété.

J'espère que cela t'aides.


+1 - Je suis tombé sur la même entrée de blog que vous, et je pense que c'est l'approche la plus propre de toutes les propositions
Tom Bushell

3
Il y a un petit impact sur les performances en utilisant cette approche, qui pourrait s'accumuler lorsqu'elle est appelée plusieurs fois. stackoverflow.com/a/747218/724944
surfen

4
Vous devez utiliser InvokeRequiredsi le code peut être exécuté avant l'affichage du contrôle ou vous aurez une exception fatale.
56ka

9

Créez un fichier ThreadSafeInvoke.snippet, puis vous pouvez simplement sélectionner les instructions de mise à jour, faire un clic droit et sélectionner `` Entourer de ... '' 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>

6

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 flexible et sans cast qui est beaucoup plus lisible tandis que le délégué dédié offre de l'efficacité.

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

4

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 d'afficher les messages de progression et (info / erreur) d'un Backroundworker copiant et diffusant des données volumineuses à partir d'une instance SQL. Chaque fois, après environ 70000 appels de progression et 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);
}

3

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

2

J'aime 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, le IsFormClosing est un champ que je définis sur True lorsque je ferme mon formulaire car il peut y avoir des threads d'arrière-plan qui sont toujours en cours d'exécution ...


-3

Vous ne devriez jamais écrire du 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 du code qui ressemble à ceci, votre application n'est pas thread-safe. Cela signifie que vous avez du code qui appelle déjà DoGUISwitch () à partir d'un autre thread. Il est trop tard pour vérifier s'il s'agit d'un autre thread. InvokeRequire doit être appelé AVANT d'appeler DoGUISwitch. Vous ne devez accéder à aucune méthode ou propriété à partir d'un thread différent.

Référence: Control.InvokeRequired Propriété où vous pouvez lire ce qui suit:

En plus de la propriété InvokeRequired, il existe quatre méthodes sur un contrôle qui sont thread-safe à appeler: Invoke, BeginInvoke, EndInvoke et CreateGraphics si le handle du contrôle a déjà été créé.

Dans une architecture à processeur unique, il n'y a pas de problème, mais dans une architecture à plusieurs processeurs, vous pouvez affecter une partie du thread de l'interface utilisateur au processeur sur lequel le code appelant s'exécutait ... et si ce processeur est différent de celui où le thread de l'interface utilisateur était alors en cours d'exécution lorsque le thread appelant se termine, Windows pensera que le thread d'interface utilisateur est terminé et tuera le processus d'application, c'est-à-dire que votre application se fermera sans erreur.


Salut, merci pour ta réponse. Cela fait des années que j'ai posé cette question (et presque tout aussi longtemps que je n'ai pas travaillé avec C #), mais je me demandais si vous pouviez expliquer un peu plus? Les documents que vous avez liés font référence à un danger spécifique d'appeler invoke()et autres avant que le contrôle ne reçoive une poignée, mais IMHO ne décrit pas ce que vous avez décrit. Le but de toutes ces invoke()absurdités est de mettre à jour l'interface utilisateur d'une manière thread-safe, et je pense que mettre plus d' instructions dans un contexte de blocage conduirait à un bégaiement? (Ugh ... content d'avoir arrêté d'utiliser la technologie M $. Tellement compliqué!)
Tom Corelis

Je tiens également à noter que malgré une utilisation fréquente du code d'origine (il y a
longtemps

3
Je doute que cette réponse soit exacte car MSDN montre de nombreux exemples, tout comme l'OP a donné.
public sans fil
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.