Reihenfolge der Ausführung des Ereignishandlers

93

Wenn ich mehrere Ereignishandler einrichte, wie folgt:

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

In welcher Reihenfolge werden die Handler ausgeführt, wenn das Ereignis RetrieveDataCompletedausgelöst wird? Werden sie im selben Thread und nacheinander in der Reihenfolge ausgeführt, in der sie registriert sind?

Phillip Ngan
quelle
2
Die Antwort ist spezifisch für das RetrieveDataCompleted-Ereignis. Wenn es den Standard-Sicherungsspeicher eines Delegierten mit mehreren Besetzungen hat, dann ja "sie werden im selben Thread und nacheinander in der Reihenfolge ausgeführt, in der sie registriert sind".
HappyNomad

Antworten:

131

Derzeit werden sie in der Reihenfolge ausgeführt, in der sie registriert sind. Dies ist jedoch ein Implementierungsdetail, und ich würde mich nicht darauf verlassen, dass dieses Verhalten in zukünftigen Versionen gleich bleibt, da es von den Spezifikationen nicht verlangt wird.

Reed Copsey
quelle
5
Ich frage mich, warum die Abstimmungen? Dies ist genau richtig und beantwortet die Frage direkt ...
Reed Copsey
2
@Rawling: Dies gilt für die Überlastungsauflösung von Binäroperatoren - nicht für die Ereignisbehandlung. Dies ist in diesem Fall nicht der Additionsoperator.
Reed Copsey
2
Ah, ich sehe, wo ich falsch liege: "Event-Handler sind Delegierte, oder?". Ich weiß jetzt, dass sie es nicht sind. Habe mir ein Ereignis geschrieben, das Handler in umgekehrter Reihenfolge abfeuert, nur um es mir selbst zu beweisen :)
Rawling
16
Zur Verdeutlichung hängt die Reihenfolge vom Hintergrundspeicher für ein bestimmtes Ereignis ab. Der Standard-Sicherungsspeicher für Ereignisse, Multi-Cast-Delegaten, wird in der Registrierungsreihenfolge als ausgeführt dokumentiert. Dies wird sich in einer zukünftigen Framework-Version nicht ändern. Was sich ändern kann, ist der für ein bestimmtes Ereignis verwendete Sicherungsspeicher.
HappyNomad
6
Abgestimmt, da es in 2 Punkten sachlich falsch ist. 1) Derzeit werden sie in der Reihenfolge ausgeführt, die die Implementierung des jeweiligen Ereignisses vorschreibt - da Sie Ihre eigenen Methoden zum Hinzufügen / Entfernen von Ereignissen implementieren können. 2) Bei Verwendung der Standardereignisimplementierung über Multi-Cast-Delegaten wird die Reihenfolge tatsächlich von den Spezifikationen verlangt.
Søren Boisen
53

Die Aufrufliste eines Delegaten ist eine geordnete Gruppe von Delegaten, in der jedes Element der Liste genau eine der vom Delegaten aufgerufenen Methoden aufruft. Eine Aufrufliste kann doppelte Methoden enthalten. Während eines Aufrufs ruft ein Delegat Methoden in der Reihenfolge auf, in der sie in der Aufrufliste angezeigt werden .

Von hier aus: Delegiertenklasse

Philip Wallace
quelle
1
Schön, aber mit den Schlüsselwörtern addund muss removeein Ereignis nicht unbedingt als Multi-Cast-Delegat implementiert werden.
HappyNomad
Wie bei Bob erwähnen die anderen Antworten, dass die Verwendung dieser Funktion mit Ereignishandlern als unzuverlässig angesehen werden sollte. Ob das nun richtig ist oder nicht, diese Antwort könnte auch darüber sprechen.
n611x007
12

Sie können die Reihenfolge ändern, indem Sie alle Handler abnehmen und dann in der gewünschten Reihenfolge wieder anbringen.

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

Die Reihenfolge ist beliebig. Sie können sich nicht darauf verlassen, dass die Handler von einem Aufruf zum nächsten in einer bestimmten Reihenfolge ausgeführt werden.

Bearbeiten: Und auch - es sei denn, dies ist nur aus Neugier - die Tatsache, dass Sie wissen müssen, weist auf ein ernstes Designproblem hin.

Rex M.
quelle
3
Die Reihenfolge hängt von der Implementierung des jeweiligen Ereignisses ab, ist jedoch nicht willkürlich. Sofern in der Dokumentation der Veranstaltung nicht die Aufrufreihenfolge angegeben ist, stimme ich zu, dass es riskant ist, sich darauf zu verlassen. In diesem Sinne habe ich eine Folgefrage gestellt .
HappyNomad
9
Ein Ereignis zu haben, das von verschiedenen Klassen in einer kreisförmigen Reihenfolge behandelt werden muss, scheint mir kein ernstes Designproblem zu sein. Das Problem tritt auf, wenn die Ereignisregistrierungen so durchgeführt werden, dass es schwierig ist, die Reihenfolge oder das Ereignis zu kennen, um zu wissen, dass die Reihenfolge wichtig ist.
Ignacio Soler Garcia
8

Sie werden in der Reihenfolge ausgeführt, in der sie registriert sind. RetrieveDataCompletedist ein Multicast-Delegierter . Ich schaue durch den Reflektor, um zu überprüfen, und es sieht so aus, als würde hinter den Kulissen ein Array verwendet, um alles im Auge zu behalten.

Bob
quelle
In den anderen Antworten wird darauf hingewiesen, dass dies bei Ereignishandlern "zufällig", "fragil", "Implementierungsdetail" usw. ist, d. h. Es wird weder von einem Standard noch von einer Konvention verlangt, es passiert einfach. ist das richtig? In jedem Fall könnte sich diese Antwort auch darauf beziehen.
n611x007
3

Wenn jemand dies im Kontext eines System.Windows.Forms.Form tun muss, finden Sie hier ein Beispiel, das die Reihenfolge des angezeigten Ereignisses invertiert.

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

Ein MulticastDelegate verfügt über eine verknüpfte Liste von Delegaten, die als Aufrufliste bezeichnet wird und aus einem oder mehreren Elementen besteht. Wenn ein Multicast-Delegat aufgerufen wird, werden die Delegaten in der Aufrufliste synchron in der Reihenfolge aufgerufen, in der sie angezeigt werden. Wenn während der Ausführung der Liste ein Fehler auftritt, wird eine Ausnahme ausgelöst.

Rahul
quelle
2

Während eines Aufrufs werden Methoden in der Reihenfolge aufgerufen, in der sie in der Aufrufliste angezeigt werden.

Aber niemand sagt, dass die Aufrufliste die Delegierten in derselben Reihenfolge verwaltet, in der sie hinzugefügt wurden. Somit ist die Aufrufreihenfolge nicht garantiert.

Ruslanu
quelle
1

Dies ist eine Funktion, mit der die neue Ereignishandlerfunktion an einer beliebigen Stelle in der Aufrufliste mit mehreren Registern platziert wird.

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

Dann können Sie die Funktion jederzeit mit einem '- =' entfernen, wo immer dies in Ihrem Code zweckmäßig ist.

PS - Ich mache keine Fehlerbehandlung für den Parameter 'position'.

chara
quelle
0

Ich hatte ein ähnliches Problem. In meinem Fall war es sehr einfach zu beheben. Ich hatte noch nie einen Delegierten gesehen, der den Operator + = nicht verwendete. Mein Problem wurde behoben, indem immer ein Delegierter am Ende hinzugefügt wurde, alle anderen immer am Anfang. Das Beispiel des OP wäre ungefähr so:

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

Im ersten Fall wird ProcessData1 zuletzt aufgerufen. Im zweiten Fall wird ProcessData2 zuerst aufgerufen.

Rechnung
quelle