Ordre d'exécution du gestionnaire d'événements

93

Si je configure plusieurs gestionnaires d'événements, comme ceci:

_webservice.RetrieveDataCompleted += ProcessData1;
_webservice.RetrieveDataCompleted += ProcessData2;

dans quel ordre les gestionnaires sont-ils exécutés lorsque l'événement RetrieveDataCompletedest déclenché? Sont-ils exécutés dans le même thread et séquentiellement dans l'ordre d'enregistrement?

Phillip Ngan
la source
2
La réponse sera spécifique à l'événement RetrieveDataCompleted. S'il possède le magasin de stockage par défaut d'un délégué multi-cast, alors oui "ils s'exécutent dans le même thread et séquentiellement dans l'ordre d'enregistrement".
HappyNomad

Réponses:

131

Actuellement, ils sont exécutés dans l'ordre dans lequel ils sont enregistrés. Cependant, il s'agit d'un détail d'implémentation, et je ne me fierais pas à ce que ce comportement reste le même dans les versions futures, car il n'est pas requis par les spécifications.

Reed Copsey
la source
5
Je me demande, pourquoi les votes négatifs? C'est exactement vrai et répond directement à la question ...
Reed Copsey
2
@Rawling: C'est pour la résolution des surcharges d'opérateurs binaires - pas pour la gestion des événements. Ce n'est pas l'opérateur d'addition, dans ce cas.
Reed Copsey
2
Ah, je vois où je me trompe: "Les gestionnaires d'événements sont des délégués, non?". Je sais maintenant que non. Je me suis écrit un événement qui déclenche les gestionnaires dans l'ordre inverse, juste pour me le prouver :)
Rawling
16
Pour clarifier, la commande dépend du magasin de support pour un événement particulier. Le magasin de stockage par défaut pour les événements, les délégués multi-cast, est documenté comme s'exécutant dans l'ordre d'inscription. Cela ne changera pas dans une future version du framework. Ce qui peut changer, c'est le magasin de support utilisé pour un événement particulier.
HappyNomad
6
Évalué car il est factuellement incorrect sur 2 points. 1) Actuellement, ils sont exécutés dans l'ordre que l'implémentation de l' événement spécifique dicte - puisque vous pouvez implémenter vos propres méthodes d'ajout / suppression pour les événements. 2) Lors de l'utilisation de l'implémentation d'événement par défaut via des délégués multi-cast, l'ordre est en fait requis par les spécifications.
Søren Boisen
53

La liste d'appel d'un délégué est un ensemble ordonné de délégués dans lequel chaque élément de la liste appelle exactement l'une des méthodes appelées par le délégué. Une liste d'appels peut contenir des méthodes en double. Lors d'un appel, un délégué appelle les méthodes dans l'ordre dans lequel elles apparaissent dans la liste d'appels .

De là: Classe des délégués

Philip Wallace
la source
1
Bien, mais en utilisant les mots add- removeclés et, un événement ne peut pas nécessairement être implémenté en tant que délégué multi-cast.
HappyNomad
comme pour Bob , les autres réponses mentionnent que l'utilisation de ceci avec les gestionnaires d'événements est quelque chose qui devrait être considéré comme peu fiable ... que ce soit vrai ou non, cette réponse pourrait également en parler.
n611x007
12

Vous pouvez modifier l'ordre en détachant tous les gestionnaires, puis en les rattachant dans l'ordre souhaité.

public event EventHandler event1;

public void ChangeHandlersOrdering()
{
    if (event1 != null)
    {
        List<EventHandler> invocationList = event1.GetInvocationList()
                                                  .OfType<EventHandler>()
                                                  .ToList();

        foreach (var handler in invocationList)
        {
            event1 -= handler;
        }

        //Change ordering now, for example in reverese order as follows
        for (int i = invocationList.Count - 1; i >= 0; i--)
        {
            event1 += invocationList[i];
        }
    }
}
Naser Asadi
la source
10

L'ordre est arbitraire. Vous ne pouvez pas compter sur l'exécution des gestionnaires dans un ordre particulier d'une invocation à l'autre.

Edit: Et aussi - à moins que ce ne soit juste par curiosité - le fait que vous ayez besoin de savoir indique un problème de conception sérieux .

Rex M
la source
3
L'ordre dépend de l'implémentation de l'événement particulier, mais il n'est pas arbitraire. À moins que la documentation de l'événement n'indique l'ordre d'appel, cependant, je reconnais qu'il est risqué d'en dépendre. Dans cette veine, j'ai posté une question de suivi .
HappyNomad
9
Avoir un événement qui doit être géré par différentes classes dans un ordre particulier ne me semble pas un problème de conception sérieux. Le problème se produira si les inscriptions à l'événement sont effectuées d'une manière qui rend difficile la connaissance de l'ordre ou l'événement pour savoir que l'ordre est important.
Ignacio Soler Garcia
8

Ils sont exécutés dans l'ordre dans lequel ils sont enregistrés. RetrieveDataCompletedest un Délégué Multicast . Je regarde à travers le réflecteur pour essayer de vérifier, et il semble qu'un tableau soit utilisé dans les coulisses pour garder une trace de tout.

Bob
la source
les autres réponses notent qu'avec les gestionnaires d'événements c'est «accidentel», «fragile», «détail d'implémentation», etc., c'est-à-dire. non requis par aucune norme ni convention, cela se produit simplement. Est-ce correct? dans tous les cas, cette réponse pourrait faire référence à cela aussi.
n611x007
3

Si quelqu'un a besoin de le faire dans le contexte d'un System.Windows.Forms.Form, voici un exemple d'inversion de l'ordre de l'événement Shown.

using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;

namespace ConsoleApplication {
    class Program {
        static void Main() {
            Form form;

            form = createForm();
            form.ShowDialog();

            form = createForm();
            invertShownOrder(form);
            form.ShowDialog();
        }

        static Form createForm() {
            var form = new Form();
            form.Shown += (sender, args) => { Console.WriteLine("form_Shown1"); };
            form.Shown += (sender, args) => { Console.WriteLine("form_Shown2"); };
            return form;
        }

        static void invertShownOrder(Form form) {
            var events = typeof(Form)
                .GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(form, null) as EventHandlerList;

            var shownEventKey = typeof(Form)
                .GetField("EVENT_SHOWN", BindingFlags.NonPublic | BindingFlags.Static)
                .GetValue(form);

            var shownEventHandler = events[shownEventKey] as EventHandler;

            if (shownEventHandler != null) {
                var invocationList = shownEventHandler
                    .GetInvocationList()
                    .OfType<EventHandler>()
                    .ToList();

                foreach (var handler in invocationList) {
                    events.RemoveHandler(shownEventKey, handler);
                }

                for (int i = invocationList.Count - 1; i >= 0; i--) {
                    events.AddHandler(shownEventKey, invocationList[i]);
                }
            }
        }
    }
}
Fábio Augusto Pandolfo
la source
2

Un MulticastDelegate a une liste chaînée de délégués, appelée liste d'appel, constituée d'un ou plusieurs éléments. Lorsqu'un délégué de multidiffusion est appelé, les délégués de la liste d'appel sont appelés de manière synchrone dans l'ordre dans lequel ils apparaissent. Si une erreur se produit pendant l'exécution de la liste, une exception est levée.

Rahul
la source
2

Lors d'un appel, les méthodes sont appelées dans l'ordre dans lequel elles apparaissent dans la liste d'appels.

Mais personne ne dit que la liste d'appels maintient les délégués dans le même ordre qu'ils sont ajoutés. Ainsi l'ordre d'appel n'est pas garanti.

ruslanu
la source
1

Il s'agit d'une fonction qui placera la nouvelle fonction de gestionnaire d'événements où vous le souhaitez dans la liste d'appels multidélégués.

    private void addDelegateAt(ref YourDelegate initial, YourDelegate newHandler, int position)
    {
        Delegate[] subscribers = initial.GetInvocationList();
        Delegate[] newSubscriptions = new Delegate[subscribers.Length + 1];

        for (int i = 0; i < newSubscriptions.Length; i++)
        {
            if (i < position)
                newSubscriptions[i] = subscribers[i];
            else if (i==position)
                newSubscriptions[i] = (YourDelegate)newHandler;
            else if (i > position)
                newSubscriptions[i] = subscribers[i-1];
        }

        initial = (YourDelegate)Delegate.Combine(newSubscriptions);
    }

Ensuite, vous pouvez toujours supprimer la fonction avec un '- =' n'importe où dans votre code.

PS - Je ne fais aucune gestion d'erreur pour le paramètre «position».

chara
la source
0

J'avais un problème similaire. Dans mon cas, il a été réglé très facilement. Je n'avais jamais vu un délégué qui n'utilisait pas l'opérateur + =. Mon problème a été résolu en ayant un délégué toujours ajouté à la fin, tous les autres sont toujours ajoutés au début. L'exemple de l'OP serait quelque chose comme:

    _webservice.RetrieveDataCompleted = _webservice.RetrieveDataCompleted + ProcessData1;
    _webservice.RetrieveDataCompleted = ProcessData2 + _webservice.RetrieveDataCompleted;

Dans le premier cas, ProcessData1 sera appelé en dernier. Dans le 2ème cas, ProcessData2 sera appelé en premier.

Facture
la source