Порядок виконання обробника подій


93

Якщо я налаштував кілька обробників подій, приблизно так:

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

в якому порядку виконуються обробники під час запуску події RetrieveDataCompleted? Чи запускаються вони в одному потоці та послідовно в тому порядку, який зареєстровано?


2
Відповідь стосуватиметься події RetrieveDataCompleted. Якщо він має типове сховище резервного копіювання делегата з кількома складаннями, тоді так "вони працюють в одному потоці та послідовно в тому порядку, який зареєстровано".
HappyNomad

Відповіді:


131

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


5
Цікаво, чому голоси проти? Це точно так, і відповідає безпосередньо на питання ...
Рід Копсі,

2
@Rawling: Це для роздільної здатності двійкового оператора - не для обробки подій. У цьому випадку це не оператор додавання.
Рід Копсі,

2
Ах, я бачу, де я помиляюся: "Обробники подій - це делегати, так?". Тепер я знаю, що вони ні. Я написав собі подію, яка звільняє хендлерів у зворотному порядку, лише щоб довести це собі :)
Роулінг,

16
Для уточнення, замовлення залежить від магазину підтримки для певної події. Зберігання резервних копій за замовчуванням для подій, делегати з декількома складовими, задокументовано як виконання в порядку реєстрації. Це не зміниться в майбутній версії фреймворку. Що може змінитися, це резервне сховище, яке використовується для певної події.
HappyNomad

6
Проголосували проти, оскільки це фактично неправильно щодо 2 пунктів. 1) На даний момент вони виконуються в тому порядку, який вимагає реалізація конкретної події - оскільки ви можете реалізувати власні методи додавання / видалення для подій. 2) При використанні реалізації події за замовчуванням за допомогою кількох делегатів, порядок фактично вимагається специфікаціями.
Søren Boisen

53

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

Звідси: Клас делегатів


1
Приємно, але використання події addта removeключових слів подія не обов’язково може бути реалізовано як мульти-кастовий делегат.
HappyNomad

як і у випадку з Бобом , в інших відповідях згадується, що використання цього з обробниками подій - це те, що слід сприймати як ненадійне ... чи правильно це, чи ні, ця відповідь могла б говорити і про це.
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, ось приклад, що інвертує порядок показаної події.

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 - Я не роблю жодної обробки помилок для параметра 'position'.


0

У мене була подібна проблема. У моєму випадку це було дуже легко виправлено. Я ніколи не бачив делегата, який не використовував оператор + =. Мою проблему було вирішено завдяки тому, що в кінці завжди додається один делегат, а всі інші завжди додаються на початку. Прикладом OP може бути приблизно такий:

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

У першому випадку ProcessData1 буде викликатися останнім. У другому випадку ProcessData2 буде викликаний першим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.