Порядок выполнения обработчика событий

94

Если я настрою несколько обработчиков событий, например:

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

в каком порядке запускаются обработчики при запуске события RetrieveDataCompleted? Выполняются ли они в одном потоке и последовательно в том порядке, в котором они зарегистрированы?

Филип Нган
источник
2
Ответ будет специфичным для события RetrieveDataCompleted. Если у него есть резервное хранилище по умолчанию для многоадресного делегата, тогда да, «они работают в одном потоке и последовательно в том порядке, в котором они зарегистрированы».
HappyNomad

Ответы:

133

В настоящее время они выполняются в том порядке, в котором они зарегистрированы. Однако это деталь реализации, и я бы не стал полагаться на то, что это поведение останется неизменным в будущих версиях, поскольку это не требуется спецификациями.

Рид Копси
источник
6
Интересно, а почему отрицательные голоса? Это абсолютно верно и прямо отвечает на вопрос ...
Рид Копси
2
@Rawling: это для разрешения перегрузки бинарного оператора, а не для обработки событий. В данном случае это не оператор сложения.
Рид Копси,
2
Ах, я понимаю, в чем я ошибаюсь: «Обработчики событий - это делегаты, верно?». Теперь я знаю, что это не так. Я написал себе событие, которое запускает обработчики в обратном порядке, просто чтобы доказать это самому себе :)
Rawling
16
Чтобы уточнить, порядок зависит от резервного магазина на конкретное мероприятие. Резервное хранилище по умолчанию для событий, многоадресные делегаты, документируются как выполняющиеся в порядке регистрации. Это не изменится в будущей версии фреймворка. Что может измениться, так это резервное хранилище, используемое для конкретного события.
HappyNomad
6
Проголосовали против, так как это фактически неверно на 2 балла. 1) В настоящее время они выполняются в том порядке, который диктуется реализацией конкретного события, поскольку вы можете реализовать свои собственные методы добавления / удаления для событий. 2) При использовании реализации события по умолчанию с помощью многоадресных делегатов порядок фактически требуется спецификациями.
Søren Boisen
53

Список вызовов делегата - это упорядоченный набор делегатов, в котором каждый элемент списка вызывает ровно один из методов, вызванных делегатом. Список вызовов может содержать повторяющиеся методы. Во время вызова делегат вызывает методы в том порядке, в котором они появляются в списке вызовов .

Отсюда: класс делегата

Филип Уоллес
источник
1
Хорошие, но используя addи removeключевые слова события не обязательно может быть реализована в виде многостраничного литой делегата в.
HappyNomad
как и в случае с Бобом , в других ответах упоминается, что использование this с обработчиками событий - это то, что следует рассматривать как ненадежное ... правильно это или нет, этот ответ тоже может говорить об этом.
n611x007
12

Вы можете изменить порядок, отсоединив все обработчики, а затем снова подключив их в желаемом порядке.

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];
        }
    }
}
Насер Асади
источник
10

Порядок произвольный. Вы не можете полагаться на то, что обработчики выполняются в каком-либо определенном порядке от одного вызова к другому.

Изменить: А также - если это не просто из любопытства - тот факт, что вам нужно знать, указывает на серьезную проблему дизайна.

Рекс М
источник
3
Порядок зависит от реализации конкретного мероприятия, но это не произвольно. Однако я согласен, если в документации события не указан порядок вызова, полагаться на него рискованно. В этом ключе я разместил дополнительный вопрос .
HappyNomad
9
Наличие события, которое должно обрабатываться разными классами в определенном порядке, не кажется мне серьезной проблемой дизайна. Проблема возникнет, если регистрация событий выполняется таким образом, что затрудняет определение порядка или события, чтобы понять, что порядок важен.
Игнасио Солер Гарсия
8

Они запускаются в том порядке, в котором они зарегистрированы. RetrieveDataCompletedявляется многоадресным делегатом . Я просматриваю отражатель, чтобы попытаться проверить, и похоже, что за кулисами используется массив, чтобы отслеживать все.

Боб
источник
в других ответах отмечается, что с обработчиками событий это «случайность», «хрупкость», «детали реализации» и т. д., т.е. не требуется никакими стандартами или соглашениями, это просто происходит. это правильно? в любом случае этот ответ может относиться и к этому.
n611x007
3

Если кому-то нужно сделать это в контексте System.Windows.Forms.Form, вот пример инвертирования порядка события 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]);
                }
            }
        }
    }
}
Фабио Аугусто Пандольфо
источник
2

MulticastDelegate имеет связанный список делегатов, называемый списком вызовов, состоящий из одного или нескольких элементов. Когда вызывается многоадресный делегат, делегаты в списке вызовов вызываются синхронно в том порядке, в котором они появляются. Если во время выполнения списка возникает ошибка, генерируется исключение.

Рахул
источник
2

Во время вызова методы вызываются в том порядке, в котором они появляются в списке вызовов.

Но никто не говорит, что список вызовов поддерживает делегатов в том же порядке, в котором они добавляются. Таким образом, порядок вызова не гарантируется.

Руслану
источник
1

Это функция, которая поместит новую функцию обработчика событий в любое место в списке вызовов множественного делегата.

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

Затем вы всегда можете удалить функцию с помощью '- =' в любом удобном месте вашего кода.

PS - Я не занимаюсь обработкой ошибок для параметра «позиция».

чара
источник
0

У меня была аналогичная проблема. В моем случае это было исправлено очень легко. Я никогда не видел делегата, который не использовал бы оператор + =. Моя проблема была исправлена ​​тем, что один делегат всегда добавлялся в конце, а все остальные всегда добавлялись в начале. Пример OP будет примерно таким:

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

В первом случае ProcessData1 будет вызываться последним. Во втором случае сначала будет вызываться ProcessData2.

Билл
источник