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


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.