Orden de ejecución del controlador de eventos

93

Si configuro varios controladores de eventos, así:

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

¿En qué orden se ejecutan los controladores cuando se activa el evento RetrieveDataCompleted? ¿Se ejecutan en el mismo hilo y secuencialmente en el orden en que están registrados?

Phillip Ngan
fuente
2
La respuesta será específica del evento RetrieveDataCompleted. Si tiene el almacenamiento de respaldo predeterminado de un delegado de multidifusión, entonces sí, "se ejecutan en el mismo hilo y secuencialmente en el orden en que están registrados".
HappyNomad

Respuestas:

131

Actualmente, se ejecutan en el orden en que se registran. Sin embargo, este es un detalle de implementación, y no confiaría en que este comportamiento se mantenga igual en futuras versiones, ya que no es requerido por las especificaciones.

Reed Copsey
fuente
5
Me pregunto, ¿por qué los votos negativos? Esto es exactamente cierto, y responde la pregunta directamente ...
Reed Copsey
2
@Rawling: Eso es para la resolución de sobrecarga del operador binario, no para el manejo de eventos. Este no es el operador de suma, en este caso.
Reed Copsey
2
Ah, veo dónde me estoy equivocando: "Los controladores de eventos son delegados, ¿verdad?". Ahora sé que no lo son. Me he escrito un evento que despide a los controladores en orden inverso, solo para demostrármelo a mí mismo :)
Rawling
16
Para aclarar, el orden depende de la tienda de respaldo para un evento en particular. La tienda de respaldo predeterminada para eventos, los delegados de transmisión múltiple, se documentan como ejecutándose en el orden de registro. Esto no cambiará en una versión futura del marco. Lo que puede cambiar es la tienda de respaldo utilizada para un evento en particular.
HappyNomad
6
Votado en contra ya que es de hecho incorrecto en 2 puntos. 1) Actualmente, se ejecutan en el orden que dicta la implementación del evento específico , ya que puede implementar sus propios métodos de agregar / eliminar para eventos. 2) Cuando se utiliza la implementación de eventos predeterminada a través de delegados de multidifusión, el orden es de hecho requerido por las especificaciones.
Søren Boisen
53

La lista de invocación de un delegado es un conjunto ordenado de delegados en el que cada elemento de la lista invoca exactamente uno de los métodos invocados por el delegado. Una lista de invocación puede contener métodos duplicados. Durante una invocación, un delegado invoca métodos en el orden en que aparecen en la lista de invocaciones .

Desde aquí: Clase delegada

Philip Wallace
fuente
1
Agradable, pero el uso de las palabras clave addy removeun evento no necesariamente se puede implementar como un delegado de transmisión múltiple.
HappyNomad
al igual que con Bob , las otras respuestas mencionan que el uso de esto con controladores de eventos es algo que debe tomarse como poco confiable ... ya sea que sea correcto o no, esta respuesta también podría hablar de eso.
n611x007
12

Puede cambiar el orden desconectando todos los controladores y volviendo a adjuntarlos en el orden deseado.

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
fuente
10

El orden es arbitrario. No puede confiar en que los controladores se ejecuten en un orden particular de una invocación a la siguiente.

Editar: Y también, a menos que sea solo por curiosidad, el hecho de que necesites saberlo es indicativo de un problema de diseño grave .

Rex M
fuente
3
El orden depende de la implementación del evento en particular, pero es no arbitraria. Sin embargo, a menos que la documentación del evento indique el orden de invocación, acepto que es riesgoso depender de él. En ese sentido, publiqué una pregunta de seguimiento .
HappyNomad
9
Tener un evento que necesita ser manejado por diferentes clases en un orden partircular no me parece un problema de diseño serio. El problema ocurrirá si los registros de eventos se realizan de una manera que dificulta conocer el orden o el evento para saber que el orden es importante.
Ignacio Soler García
8

Se ejecutan en el orden en que están registrados. RetrieveDataCompletedes un delegados de multidifusión . Estoy mirando a través del reflector para intentar verificar, y parece que se usa una matriz detrás de escena para realizar un seguimiento de todo.

Beto
fuente
las otras respuestas señalan que con los controladores de eventos esto es 'accidental', 'frágil', 'detalle de implementación', etc., es decir. no es requerido por ningún estándar o convención, simplemente sucede. ¿está bien? en cualquier caso, esta respuesta también podría referirse a eso.
n611x007
3

Si alguien necesita hacer esto en el contexto de un System.Windows.Forms.Form, aquí hay un ejemplo que invierte el orden del evento 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
fuente
2

Un MulticastDelegate tiene una lista vinculada de delegados, denominada lista de invocación, que consta de uno o más elementos. Cuando se invoca un delegado de multidifusión, los delegados de la lista de invocación se llaman sincrónicamente en el orden en que aparecen. Si ocurre un error durante la ejecución de la lista, se lanza una excepción.

Rahul
fuente
2

Durante una invocación, los métodos se invocan en el orden en que aparecen en la lista de invocaciones.

Pero nadie dice que la lista de invocación mantenga a los delegados en el mismo orden en que se agregan. Por tanto, el orden de invocación no está garantizado.

ruslanu
fuente
1

Esta es una función que colocará la nueva función de controlador de eventos donde desee en la lista de invocaciones de múltiples delegados.

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

Entonces siempre puede eliminar la función con un '- =' donde sea conveniente en su código.

PD: no estoy haciendo ningún manejo de errores para el parámetro 'posición'.

chara
fuente
0

Tuve un problema similar. En mi caso se solucionó muy fácilmente. Nunca había visto a un delegado que no usara el operador + =. Mi problema se solucionó al tener un delegado siempre agregado al final, todos los demás siempre se agregan al principio. El ejemplo del OP sería algo como:

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

En el primer caso, ProcessData1 se llamará en último lugar. En el segundo caso, ProcessData2 se llamará primero.

Cuenta
fuente