Invoke (délégué)

93

Quelqu'un peut-il s'il vous plaît expliquer cette déclaration écrite sur ce lien

Invoke(Delegate):

Exécute le délégué spécifié sur le thread qui possède le handle de fenêtre sous-jacent du contrôle.

Quelqu'un peut-il expliquer ce que cela signifie (en particulier le gras) Je ne suis pas en mesure de le comprendre clairement

user1903439
la source
4
La réponse à cette question est liée à la propriété Control.InvokeRequired - voir msdn.microsoft.com/en-us/library/…
tiret

Réponses:

130

La réponse à cette question réside dans le fonctionnement des contrôles C #

Les contrôles dans Windows Forms sont liés à un thread spécifique et ne sont pas thread-safe. Par conséquent, si vous appelez la méthode d'un contrôle à partir d'un thread différent, vous devez utiliser l'une des méthodes d'appel du contrôle pour marshaler l'appel vers le thread approprié. Cette propriété peut être utilisée pour déterminer si vous devez appeler une méthode invoke, ce qui peut être utile si vous ne savez pas quel thread possède un contrôle.

De Control.InvokeRequired

En fait, ce que fait Invoke est de s'assurer que le code que vous appelez se produit sur le thread sur lequel le contrôle «vit», empêchant efficacement les exceptions entre threads.

D'un point de vue historique, dans .Net 1.1, cela était en fait autorisé. Cela signifiait que vous pouviez essayer d'exécuter du code sur le thread "GUI" à partir de n'importe quel thread d'arrière-plan et cela fonctionnerait principalement. Parfois, cela provoquait simplement la fermeture de votre application parce que vous interrompiez effectivement le thread GUI pendant qu'il faisait autre chose. Il s'agit de l' exception de threads croisés - imaginez que vous essayez de mettre à jour une zone de texte pendant que l'interface graphique peint autre chose.

  • Quelle action est prioritaire?
  • Est-il même possible que les deux se produisent en même temps?
  • Qu'arrive-t-il à toutes les autres commandes que l'interface graphique doit exécuter?

En effet, vous interrompez une file d'attente, ce qui peut avoir de nombreuses conséquences imprévues. Invoke est en fait le moyen «poli» d'obtenir ce que vous voulez faire dans cette file d'attente, et cette règle a été appliquée à partir de .Net 2.0 via une exception InvalidOperationException. .

Pour comprendre ce qui se passe réellement dans les coulisses, et ce que l'on entend par "fil GUI", il est utile de comprendre ce qu'est une pompe de message ou une boucle de message.

Ceci est en fait déjà répondu à la question " Qu'est-ce qu'une pompe à messages " et il est recommandé de le lire pour comprendre le mécanisme réel auquel vous vous connectez lorsque vous interagissez avec les contrôles.

D'autres lectures que vous pourriez trouver utiles incluent:

Quoi de neuf avec Begin Invoke

L'une des règles cardinales de la programmation de l'interface graphique Windows est que seul le thread qui a créé un contrôle peut accéder et / ou modifier son contenu (à l'exception de quelques exceptions documentées). Essayez de le faire à partir de n'importe quel autre thread et vous obtiendrez un comportement imprévisible allant du blocage aux exceptions en passant par une interface utilisateur à moitié mise à jour. La bonne façon de mettre à jour un contrôle à partir d'un autre thread est alors de publier un message approprié dans la file d'attente de messages de l'application. Lorsque la pompe de message arrive à exécuter ce message, le contrôle sera mis à jour, sur le même thread qui l'a créé (rappelez-vous, la pompe de message s'exécute sur le thread principal).

et, pour un aperçu plus lourd du code avec un échantillon représentatif:

Opérations cross-thread non valides

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

Une fois que vous aurez compris InvokeRequired, vous souhaiterez peut-être envisager d'utiliser une méthode d'extension pour encapsuler ces appels. Ceci est bien couvert dans la question de débordement de pile Nettoyage du code jonché avec Invoke requis .

Il y a aussi un autre article sur ce qui s'est passé historiquement qui peut être intéressant.

tiret
la source
68

Un objet de contrôle ou de fenêtre dans Windows Forms est juste un wrapper autour d'une fenêtre Win32 identifiée par un handle (parfois appelé HWND). La plupart des opérations que vous effectuez avec le contrôle aboutiront finalement à un appel d'API Win32 qui utilise ce handle. Le handle appartient au thread qui l'a créé (généralement le thread principal) et ne doit pas être manipulé par un autre thread. Si, pour une raison quelconque, vous devez faire quelque chose avec le contrôle d'un autre thread, vous pouvez utiliser Invokepour demander au thread principal de le faire en votre nom.

Par exemple, si vous souhaitez modifier le texte d'une étiquette à partir d'un thread de travail, vous pouvez faire quelque chose comme ceci:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));
Thomas Levesque
la source
Pouvez-vous expliquer pourquoi quelqu'un ferait quelque chose comme ça? this.Invoke(() => this.Enabled = true);Tout ce thisà quoi se réfère est sûrement dans le fil actuel, non?
Kyle Delaney
1
@KyleDelaney, un objet n'est pas "dans" un thread, et le thread actuel n'est pas nécessairement le thread qui a créé l'objet.
Thomas Levesque
24

Si vous souhaitez modifier un contrôle, cela doit être fait dans le thread dans lequel le contrôle a été créé. Cette Invokeméthode vous permet d'exécuter des méthodes dans le thread associé (le thread qui possède le handle de fenêtre sous-jacent du contrôle).

Dans l'exemple ci-dessous, thread1 lève une exception car SetText1 tente de modifier textBox1.Text à partir d'un autre thread. Mais dans thread2, l'action dans SetText2 est exécutée dans le thread dans lequel la zone de texte a été créée

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"));
}
Mehmet Ataş
la source
J'aime vraiment cette approche, elle cache la nature des délégués, mais c'est quand même un bon raccourci.
shytikov
6
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });
BestDeveloper
la source
L'utilisation de System.Action que les gens suggèrent sur d'autres réponses ne fonctionne que sur le framework 3.5+, pour les versions plus anciennes, cela fonctionne parfaitement
Suicide Platypus
2

En termes pratiques, cela signifie que le délégué est garanti d'être appelé sur le thread principal. Ceci est important car dans le cas des contrôles Windows, si vous ne mettez pas à jour leurs propriétés sur le thread principal, vous ne voyez pas la modification ou le contrôle déclenche une exception.

Le modèle est:

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)
}
satnhak
la source
2

this.Invoke(delegate)assurez-vous que vous appelez le délégué à l'argument sur this.Invoke()le thread principal / le thread créé.

Je peux dire qu'une règle Thumb n'accède pas à vos contrôles de formulaire sauf à partir du thread principal.

Peut-être que les lignes suivantes ont du sens pour utiliser Invoke ()

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

Il y a des situations où vous créez un thread Threadpool (c'est-à-dire un thread de travail), il s'exécutera sur le thread principal. Il ne créera pas de nouveau thread car le thread principal est disponible pour traiter d'autres instructions. Commencez par vérifier si le thread en cours d'exécution est le thread principal en utilisant this.InvokeRequiredif retourne true le code actuel est en cours d'exécution sur le thread de travail, alors appelez this.Invoke (d, new object [] {text});

Sinon, mettez directement à jour le contrôle de l'interface utilisateur (ici, vous êtes assuré d'exécuter le code sur le thread principal.)

Srikanth
la source
1

Cela signifie que le délégué s'exécutera sur le thread d'interface utilisateur, même si vous appelez cette méthode à partir d'un worker en arrière-plan ou d'un thread de pool de threads. Les éléments de l'interface utilisateur ont une affinité de thread - ils aiment uniquement parler directement à un thread: le thread de l'interface utilisateur. Le thread d'interface utilisateur est défini comme le thread qui a créé l'instance de contrôle et est par conséquent associé au handle de fenêtre. Mais tout cela est un détail de mise en œuvre.

Le point clé est le suivant: vous appelleriez cette méthode à partir d'un thread de travail afin de pouvoir accéder à l'interface utilisateur (pour modifier la valeur d'une étiquette, etc.) - puisque vous n'êtes pas autorisé à le faire à partir d'un autre thread que le thread de l'interface utilisateur.

Marc Gravell
la source
0

Les délégués sont essentiellement en ligne Actionou Func<T>. Vous pouvez déclarer un délégué en dehors de la portée d'une méthode que vous exécutez ou en utilisant une lambdaexpression ( =>); parce que vous exécutez le délégué dans une méthode, vous l'exécutez sur le thread qui est en cours d'exécution pour la fenêtre / application en cours qui est le bit en gras.

Exemple Lambda

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

Cela signifie que le délégué que vous passez est exécuté sur le thread qui a créé l'objet Control (qui est le thread d'interface utilisateur).

Vous devez appeler cette méthode lorsque votre application est multithread et que vous souhaitez effectuer une opération d'interface utilisateur à partir d'un thread autre que le thread d'interface utilisateur, car si vous essayez simplement d'appeler une méthode sur un contrôle à partir d'un thread différent, vous obtiendrez un System.InvalidOperationException.

user1610015
la source