Kolejność wykonywania procedury obsługi zdarzeń

93

Jeśli skonfiguruję wiele programów obsługi zdarzeń, na przykład:

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

w jakiej kolejności są uruchamiane programy obsługi, gdy zdarzenie RetrieveDataCompletedjest uruchamiane? Czy są uruchamiane w tym samym wątku i sekwencyjnie w kolejności, w jakiej zostały zarejestrowane?

Phillip Ngan
źródło
2
Odpowiedź będzie specyficzna dla zdarzenia RetrieveDataCompleted. Jeśli ma domyślny magazyn zapasowy delegata multiemisji, to tak, „działają w tym samym wątku i sekwencyjnie w kolejności, w jakiej zostały zarejestrowane”.
HappyNomad

Odpowiedzi:

133

Obecnie są one realizowane w kolejności, w jakiej zostały zarejestrowane. Jest to jednak szczegół implementacji i nie polegałbym na tym, że to zachowanie pozostanie takie samo w przyszłych wersjach, ponieważ nie jest to wymagane przez specyfikacje.

Reed Copsey
źródło
6
Zastanawiam się, dlaczego głosy przeciw? To jest dokładnie prawda i odpowiada bezpośrednio na pytanie ...
Reed Copsey
2
@Rawling: To dotyczy rozwiązania przeciążenia operatora binarnego - nie obsługi zdarzeń. W tym przypadku nie jest to operator dodawania.
Reed Copsey
2
Ach, widzę, do czego zmierzam źle: „Programy obsługi zdarzeń to delegaci, prawda?”. Teraz wiem, że nie. Napisałem sobie zdarzenie, które wystrzeliwuje handlerów w odwrotnej kolejności, tylko po to, aby sobie to udowodnić :)
Rawling
16
Aby wyjaśnić, kolejność zależy od magazynu wspierającego dane wydarzenie. Domyślny magazyn zapasowy dla zdarzeń, delegatów multiemisji, jest udokumentowany jako wykonywany w kolejności rejestracji. Nie zmieni się to w przyszłej wersji frameworka. To, co może się zmienić, to magazyn zapasowy używany na potrzeby konkretnego wydarzenia.
HappyNomad
6
Negocjowany, ponieważ jest niezgodny ze stanem faktycznym w 2 punktach. 1) Obecnie są one wykonywane w kolejności, jaką dyktuje realizacja danego zdarzenia - ponieważ można zaimplementować własne metody dodawania / usuwania zdarzeń. 2) W przypadku korzystania z domyślnej implementacji zdarzenia za pośrednictwem delegatów transmisji grupowej kolejność jest faktycznie wymagana przez specyfikacje.
Søren Boisen
53

Lista wywołań delegata to uporządkowany zestaw delegatów, w którym każdy element listy wywołuje dokładnie jedną z metod wywoływanych przez delegata. Lista wywołań może zawierać zduplikowane metody. Podczas wywołania delegat wywołuje metody w kolejności, w jakiej pojawiają się na liście wywołań .

Stąd: klasa delegatów

Philip Wallace
źródło
1
Ładna, ale używając addi removeHasła nie muszą być wdrażane jako delegat grupowej zdarzenie.
HappyNomad
podobnie jak w przypadku Boba , inne odpowiedzi wspominają, że użycie tego z programami obsługi zdarzeń jest czymś, co powinno być traktowane jako niewiarygodne ... czy to prawda, czy nie, ta odpowiedź może również mówić o tym.
n611x007
12

Możesz zmienić kolejność, odłączając wszystkie programy obsługi, a następnie podłączając je ponownie w żądanej kolejności.

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
źródło
10

Kolejność jest dowolna. Nie można polegać na tym, że programy obsługi będą wykonywane w określonej kolejności od jednego wywołania do następnego.

Edycja: A także - chyba że jest to tylko z ciekawości - fakt, że musisz wiedzieć, wskazuje na poważny problem projektowy.

Rex M
źródło
3
Kolejność zależy od realizacji określonego zdarzenia, ale to nie arbitralne. Jeśli jednak dokumentacja wydarzenia nie wskazuje kolejności wywołania, zgadzam się, że poleganie na nim jest ryzykowne. W tym duchu zadałem kolejne pytanie .
HappyNomad
9
Zdarzenie, które musi być obsługiwane przez różne klasy w częściowej kolejności, nie wydaje mi się poważnym problemem projektowym. Problem wystąpi, jeśli rejestracje wydarzeń są wykonywane w sposób, który utrudnia poznanie kolejności lub wydarzenia, aby wiedzieć, że kolejność jest ważna.
Ignacio Soler Garcia
8

Są uruchamiane w kolejności, w jakiej zostały zarejestrowane. RetrieveDataCompletedjest delegatem multiemisji . Patrzę przez reflektor, aby spróbować zweryfikować, i wygląda na to, że tablica jest używana za kulisami, aby śledzić wszystko.

Pion
źródło
w innych odpowiedziach zauważono, że w przypadku obsługi zdarzeń jest to „przypadkowe”, „delikatne”, „szczegóły implementacji” itd., tj. nie jest wymagane przez żadną normę ani konwencję, po prostu się dzieje. czy to prawda? w każdym razie ta odpowiedź może również odnosić się do tego.
n611x007
3

Jeśli ktoś musi to zrobić w kontekście System.Windows.Forms.Form, oto przykład odwrócenia kolejności zdarzenia 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
źródło
2

MulticastDelegate ma połączoną listę delegatów, nazywaną listą wywołań, składającą się z co najmniej jednego elementu. Gdy delegat multiemisji jest wywoływany, delegaci na liście wywołań są wywoływani synchronicznie w kolejności, w jakiej się pojawiają. Jeśli podczas wykonywania listy wystąpi błąd, zostanie zgłoszony wyjątek.

Rahul
źródło
2

Podczas wywołania metody są wywoływane w kolejności, w jakiej pojawiają się na liście wywołań.

Ale nikt nie mówi, że lista wywołań utrzymuje delegatów w tej samej kolejności, w jakiej są dodawani. W ten sposób kolejność wywołań nie jest gwarantowana.

ruslanu
źródło
1

Jest to funkcja, która umieści nową funkcję obsługi zdarzeń w dowolnym miejscu na liście wywołań z wieloma bramkami.

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

Następnie zawsze możesz usunąć funkcję za pomocą „- =” w dowolnym miejscu w kodzie.

PS - nie wykonuję żadnej obsługi błędów dla parametru „pozycja”.

chara
źródło
0

Miałem podobny problem. W moim przypadku naprawiono to bardzo łatwo. Nigdy nie widziałem delegata, który nie używałby operatora + =. Mój problem został rozwiązany, ponieważ jeden delegat był zawsze dodawany na końcu, a wszyscy inni są zawsze dodawani na początku. Przykład PO wyglądałby tak:

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

W pierwszym przypadku ProcessData1 zostanie wywołany jako ostatni. W drugim przypadku ProcessData2 zostanie wywołana jako pierwsza.

Rachunek
źródło