Ordem de execução do manipulador de eventos

93

Se eu configurar vários manipuladores de eventos, como:

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

em que ordem os manipuladores são executados quando o evento RetrieveDataCompletedé disparado? Eles são executados no mesmo thread e sequencialmente na ordem em que são registrados?

Phillip Ngan
fonte
2
A resposta será específica para o evento RetrieveDataCompleted. Se ele tiver o armazenamento de apoio padrão de um delegado multi-cast, então sim "eles são executados no mesmo encadeamento e sequencialmente na ordem em que são registrados".
HappyNomad

Respostas:

131

Atualmente, eles são executados na ordem em que são registrados. No entanto, este é um detalhe de implementação, e eu não confiaria que esse comportamento permanecesse o mesmo em versões futuras, uma vez que não é exigido pelas especificações.

Reed Copsey
fonte
5
Eu me pergunto, por que os votos negativos? Isso é exatamente verdade, e responde a pergunta diretamente ...
Reed Copsey
2
@Rawling: Isso é para resolução de sobrecarga do operador binário - não manipulação de eventos. Este não é o operador de adição, neste caso.
Reed Copsey
2
Ah, eu vejo onde estou errado: "Os manipuladores de eventos são delegados, certo?". Agora sei que não. Escrevi para mim mesmo um evento que dispara manipuladores na ordem reversa, apenas para provar a mim mesmo :)
Rawling
16
Para esclarecer, o pedido depende da loja de apoio para um determinado evento. O armazenamento de apoio padrão para eventos, delegados multi-cast, são documentados como sendo executados na ordem de registro. Isso não mudará em uma versão futura do framework. O que pode mudar é a loja de apoio usada para um determinado evento.
HappyNomad
6
Não votou porque é factualmente incorreto em 2 pontos. 1) Atualmente, eles são executados na ordem ditada pela implementação do evento específico - uma vez que você pode implementar seus próprios métodos de adição / remoção para eventos. 2) Ao usar a implementação de evento padrão por meio de delegados multicast, a ordem é de fato exigida pelas especificações.
Søren Boisen 01 de
53

A lista de invocação de um delegado é um conjunto ordenado de delegados em que cada elemento da lista invoca exatamente um dos métodos invocados pelo delegado. Uma lista de invocação pode conter métodos duplicados. Durante uma invocação, um delegado invoca métodos na ordem em que aparecem na lista de invocação .

A partir daqui: classe de delegados

Philip Wallace
fonte
1
Legal, mas usando as palavras-chave adde, removeum evento pode não ser necessariamente implementado como um delegado multi-cast.
HappyNomad
como no caso de Bob , as outras respostas mencionam que o uso disso com manipuladores de eventos é algo que não deve ser considerado confiável ... esteja certo ou não, essa resposta pode falar sobre isso também.
n611x007
12

Você pode alterar a ordem desanexando todos os manipuladores e, em seguida, reconectando na ordem desejada.

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

A ordem é arbitrária. Você não pode contar com os manipuladores sendo executados em qualquer ordem específica de uma chamada para a próxima.

Edit: E também - a menos que seja apenas por curiosidade - o fato de que você precisa saber é indicativo de um sério problema de design.

Rex M
fonte
3
A ordem depende da implementação do evento particular, mas é não arbitrária. A menos que a documentação do evento indique a ordem de invocação, porém, concordo que é arriscado depender dela. Nesse sentido, postei uma pergunta de acompanhamento .
HappyNomad
9
Ter um evento que precisa ser tratado por diferentes classes em uma ordem partircular não parece um problema de design sério para mim. O problema ocorrerá se os registros do evento forem feitos de forma que dificulte o conhecimento do pedido ou evento para saber que o pedido é importante.
Ignacio Soler Garcia
8

Eles são executados na ordem em que são registrados. RetrieveDataCompletedé um Multicast Delegates . Estou olhando pelo refletor para tentar verificar, e parece que um array é usado nos bastidores para controlar tudo.

Prumo
fonte
as outras respostas observam que, com manipuladores de eventos, isso é 'acidental', 'frágil', 'detalhe de implementação', etc., ou seja. não exigido por nenhum padrão ou convenção, simplesmente acontece. Isso está certo? em qualquer caso, esta resposta também pode referir-se a isso.
n611x007
3

Se alguém precisar fazer isso no contexto de um System.Windows.Forms.Form, aqui está um exemplo invertendo a ordem do evento Mostrado.

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
fonte
2

Um MulticastDelegate tem uma lista vinculada de delegados, chamada de lista de invocação, que consiste em um ou mais elementos. Quando um delegado multicast é invocado, os delegados na lista de invocação são chamados de forma síncrona na ordem em que aparecem. Se ocorrer um erro durante a execução da lista, uma exceção será lançada.

Rahul
fonte
2

Durante uma chamada, os métodos são chamados na ordem em que aparecem na lista de chamadas.

Mas ninguém diz que a lista de invocação mantém os delegados na mesma ordem em que são adicionados. Assim, a ordem de invocação não é garantida.

Ruslanu
fonte
1

Esta é uma função que colocará a nova função do manipulador de eventos onde você quiser na lista de invocação multidelegate.

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

Então você sempre pode remover a função com um '- =' em qualquer lugar do seu código.

PS - Não estou tratando de erros para o parâmetro 'posição'.

chara
fonte
0

Eu tive um problema parecido. No meu caso, foi corrigido muito facilmente. Eu nunca tinha visto um delegado que não usasse o operador + =. Meu problema foi resolvido por ter um delegado sempre adicionado no final, todos os outros são sempre adicionados no início. O exemplo do OP seria algo como:

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

No primeiro caso, ProcessData1 será chamado por último. No segundo caso, ProcessData2 será chamado primeiro.

Conta
fonte