Fragensteller
Abarbeitung von Events in Visual C#

Frage
-
Hallo allerseits, mir ist die genaue Funktion von Events nicht ganz klar: Was geschieht, wenn in meiner C#-Anwendung ein Event ausgelöst wird? Legt die Laufzeitumgebung das Event auf eine Art Stack und arbeitet es ab, wenn es gerade Gelegenheit dazu gibt, oder wird ein Event _sofort_ abgearbeitet, d.h. wird die Methode, die mit dem Event verknüpft ist, sofort aufgerufen und das Programm macht erst danach an der Stelle weiter, an der das Event ausgelöst wurde? Was passiert, wenn ein Event mehr als einen Abonnenten hat? Ich hoffe, meine Frage war einigermaßen verständlich. Danke für alle Infos Kubadoo
Alle Antworten
-
Hallo K,
die 'normalen' events (ein C# keyword) werden sofort abgearbeitet, und sind intern eine Liste von Abonnenten (eine Art von Multicast-Delegate),
welche sequentiell - aber in keiner verbindlichen Reihenfolge aufgerufen werden.
(irgendwelche 'asynchrone' Vorgänge gibt es hier von alleine also nicht, wären ggf aber auch machbar)
Details:
http://msdn.microsoft.com/de-de/library/8627sbea.aspx
http://msdn.microsoft.com/en-us/library/system.multicastdelegate.getinvocationlist.aspx
aber all dies findet sich garantiert auch in jedem C# Lehrbuch -
Hallo K.,
Die Delegaten werden in der (verbindlichen) Reihenfolge synchron aufgerufen, in der sie im InvocationList Array (quasi eine Liste der Handler/Delegaten der Abonnenten ->Aufrufliste) auftreten. Das ist normal die Reihenfolge, in der sie dem MulticastDelegate zugefügt wurden ("+="). Nach Auslösen des Events werden die zugeordneten Handler (alle synchron) quasi "sofort" abgearbeitet (einfach als Methodenaufruf(e) vorstellen). Es kann natürlich sein, dass sich eine Methode eines anderen Threads dazwischenschiebt, aber bzgl. dieses einen Threads direkt nach Auslösen des Events (quasi Methodenaufruf).
Hier ein Beispiel, woran Du das prüfen kannst:event EventHandler DemoEvent; private void Form1_Load(object sender, EventArgs e) { DemoEvent += DemoHandler1; DemoEvent += DemoHandler2; DemoEvent(null, EventArgs.Empty); // Auslösen des Events Console.WriteLine("Nach dem Auslösen des Handlers"); } void DemoHandler1(object sender, EventArgs e) { Thread.Sleep(1000); Console.WriteLine("DemoHandler1"); } void DemoHandler2(object sender, EventArgs e) { Console.WriteLine("DemoHandler2"); }
[MulticastDelegate-Klasse (System)]
http://msdn.microsoft.com/de-de/library/system.multicastdelegate.aspx[Ereignisse (C#-Programmierhandbuch)]
http://msdn.microsoft.com/de-de/library/awbftdfh.aspx
ciao Frank- Bearbeitet Frank Dzaebel Mittwoch, 25. August 2010 09:43
-
die 'normalen' events (ein C# keyword) werden sofort abgearbeitet, und sind intern eine Liste von Abonnenten (eine Art von Multicast-Delegate),
welche sequentiell - aber in keiner verbindlichen Reihenfolge aufgerufen werden.
zur 'Reihenfolge' achte man hier insbesondere auf die Tatsache,
dass die Standards (C#/CLI, ECMA/ISO) für das 'event' keyword
nicht explizit die Microsoft .NET Klasse 'MulticastDelegate' für die interne _Implementation_ vorschreiben,
plus C# 'event' selber auch keine Reihenfolge spezifiziert!
Daher gilt von der MSDN Implementationsbeschreibung zu MD:
"...werden die Delegaten in der Reihenfolge synchron aufgerufen, in der sie in der Liste aufgeführt sind."
http://msdn.microsoft.com/de-de/library/system.multicastdelegate.aspx
kein _verbindlicher_ Umkehrschluss auf C# 'event'.
Quellen:
http://msdn.microsoft.com/en-us/vcsharp/bb508935.aspx
"Generally, delegates are called in the order they were added
but this behavior is not specified within the CLI specification and furthermore,
it can be overridden.
Therefore, programmers should not depend on an invocation order"http://stackoverflow.com/questions/1645478/order-of-event-handler-execution
"this is an implementation detail"Microsoft Dave Reed (InfinitiesLoop) Blog [comment]
http://weblogs.asp.net/infinitiesloop/archive/2009/01/14/the-event-handler-that-cried-wolf.aspx
"events dont have to use multicast delegates though, so you really shouldnt depend on the behavior"
Von dem her ist das 'Default-Verhalten' bei aktuellen .NET-Versionen 'dokumentiert', aber _nicht_ verbindlich. -
bzgl. "verbindlicher Reihenfolge" in C# noch dazu:
... wer der von mir angegebenen MSDN-Doku diesbzgl. nicht glaubt ;-)
der kann es auch noch einmal hier nachlesen:[Download details: C# Language Specification 4.0]
http://www.microsoft.com/downloads/details.aspx?FamilyID=dfbf523c-f98c-4804-afbd-459e846b268e[->15.4 Delegate invocation]
"Invocation of a delegate instance whose invocation list contains
multiple entries proceeds by invoking each of the methods in the
invocation list, synchronously, in order." (zu Deutsch: in Reihenfolge)
oder auch die global gültige ECMA-Spezifikation, die auch Mono oder andere Implementationen von C# benutzen:
[C# LANGUAGE SPECIFICATION]
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf[-> 22.3 Delegate invocation]
"Invocation of a delegate instance whose invocation list
contains multiple entries, proceeds by invoking each
of the methods in the invocation list, synchronously, in order."
_________Oder auch aus der MSDN:
"Wenn ein Multicastdelegat aufgerufen wird, werden die
Delegaten in der Reihenfolge synchron aufgerufen, in der
sie in der Liste aufgeführt sind".[Quelle: MulticastDelegate-Klasse (System)]
http://msdn.microsoft.com/de-de/library/system.multicastdelegate.aspx
_________
[Creating Custom Delegates and Events in C#]
"When an event is raised, all the delegates in the invocation
list are invoked in the order they were added."
_________
[Delegate.Combine-Methode (Delegate, Delegate) (System)]
"Eine neuer Delegat mit einer Aufrufliste, die aus der Verkettung
der Aufruflisten von a und b in dieser Reihenfolge besteht"
_________
Oder auch ein Community-Artikel:
".. delegates are called in the order they were added ...
but this behavior is not specified within the CLI specification"
sagt richtig, dass dieses Verhalten nicht in der CLI Spec drin ist. In der C# Spec ist dies aber verbindlich. Für öffentlich Delegates in Klassenbibliotheken (auch in C#) sollte man auf jeden Fall eine Rücksicht auf die Reihenfolge vermeiden, denn dann könnte die Bibliothek nicht in anderen Sprachen eingesetzt werden.
Intern kann man aber das dokumentierte verbindliche Verhalten in C# ausnutzen.
Dennoch sollte das Bezugnehmen auf die verbindliche Reihenfolge eher eine Seltenheit bleiben, nur, wenn es einen spürbaren Vorteil bringt.
ciao Frank -
Hallo Kubadoo,
Was geschieht, wenn in meiner C#-Anwendung ein Event ausgelöst wird? Legt die Laufzeitumgebung das Event auf eine Art Stack und arbeitet es ab, wenn es gerade Gelegenheit dazu gibt, oder wird ein Event _sofort_ abgearbeitet, d.h. wird die Methode, die mit dem Event verknüpft ist, sofort aufgerufen und das Programm macht erst danach an der Stelle weiter, an der das Event ausgelöst wurde? Was passiert, wenn ein Event mehr als einen Abonnenten hat?
Ich will mal versuchen, anhand des von Frank geposteten Codes zu erklären, wie das Ganze zusammenhängt. EventHandler ist von MulticastDelegate abgeleitet, das ein privates Feld _invocationList besitzt. In diesem Array werden die einzelnen hinzugefügten Handler gespeichert und dieses Array ist es auch, das sequentiell, d.h. in Indexreihenfolge abgearbeitet wird.
Wenn der Compiler auf die unscheinbare Zeile "event EventHandler DemoEvent;" trifft, macht er folgendes:
1. Erstellt ein Feld vom Typ EventHandler in der Klasse die das Ereignis enthält
2. Erstellt zwei sog. Accessor-Methoden: add_DemoEvent und remove_DemoEvent.
3. Da sowohl das Feld als auch die Methoden als privat deklariert sind, und somit nach außen nicht sichtbar sind, wird nun ein öffentliches IL-Konstrukt event erstellt, dessen Hauptaufgabe darin besteht, eine Facade zu den Accessor-Methoden zu bieten.
Wenn man nun dem Ereignis z.B. einen neuen Handler hinzufügt:
DemoEvent += DemoHandler2;
wird diese Anforderung an die private Accessor-Methode weitergeleitet. Die Methode add_DemoEvent erhält nun eine EventHandler-Variable und ruft System.Delegate.Combine auf, um die InvocationList(s) des übergebenen EventHandler-Delegaten mit der InvocationList des privaten EventHandler-Felds zu verketten und einen neuen EventHandler-Delegaten zurückzugeben. Die Verkettung geschieht in der Reihenfolge in der die Parameter angegeben wurden [System.Delegate.Combine(a, b)], d.h. zurückgegeben wird laut MSDN "Ein neuer Delegat mit einer Aufrufliste, die aus der Verkettung der Aufruflisten von a und b *in dieser Reihenfolge* besteht".
Die sequentielle Reihenfolge bei der Bearbeitung ergibt sich also einerseits daraus, dass die InvokationList als Array abgearbeitet wird (idx++), andererseits daraus, dass bei der Verkettung von Delegaten immer die Reihenfolge der Argumente berücksichtigt wird.Allerdings sollte man die Logik der eigenen Anwendung nicht auf die Aufrufreihenfolge basieren, da dies wirklich ein Implementationsdetail ist, und zudem Ereignisse sehr anfällig gegenüber Threading-Problemen sind. Wenn man z.B. in Franks Code in einem neuen Thread die Aufrufliste zwischen dem Hinzufügen von DemoHandler1 und dem Aufruf von DemoEvent leert, würde es entsprechend krachen. Aber noch viel schlimmer würde es kommen, wenn DemoEvent nun Methoden aufrufen würde, die es gar nicht selbst zur InvokationList hinzugefügt hat, oder wenn die Logik der eigenen Anwendung darauf beruht, dass es eine gewisse Reihenfolge der Abarbeitung gibt. Denn die Realität könnte im Multithreading-Betrieb etwa so aussehen:
Thread 1: DemoHandler1 wurde aufgerufen
Thread 2: DemoHandler 3 wurde aufgerufen
Thread 1: DemoHandler 2 wurde aufgerufen
Gruß
Marcel -
> die Standards (C#/CLI, ECMA/ISO)
Dass Dokumente wie "C# Language Specification 4.0" bloss die Microsoft-Implementation (und Zukunftswünsche) spezifizieren ist wohl jedem klar, was natürlich auch die _Präzisierung_ eines implementierten Verhaltens beinhalten kann.
Dadurch wird es aber noch lange kein Standard oder global verbindlich.Aber die dort präzisierten _Delegates_ sind hier ja gar nicht das eigentliche Thema!
Viel entscheidender ist:> kein _verbindlicher_ Umkehrschluss auf C# 'event'.
offenbar ist es nötig dies nochmals zu unterstreichen,
denn ab all dem Gerede zur Implementation per Delegate/MD-InvocationList wird vergessen,
dass man das Thema insbesondere aus der Sicht des 'event'-Konsumenten/Abonnenten sehen muss!Denn das C# 'event' keyword spezifiziert (insbesondere in den Standards) bloss eine Art 'neutrale Fassade'!
Und es ist ja nicht der Event-Konsument, der den Event auslöst und die (private) InvocationList verwaltet/aufruft (oder gar alles mit add/remove-accessors total anders löst!),
sondern dies macht einzig die interne Implementation der Klasse, die den Event veröffentlicht.Somit darf ein Event-Konsument von aussen keine _verbindliche_ Annahmen über den (non-public!) Aufbau/Handling des Events machen.
In allen alltäglichen Anwendungsfällen von Events im .NET-Umfeld spielt die Reihenfolge auch gar keine Rolle (bzw sind eh bloss 1:1).
Natürlich könnte man selber eine Klasse mit einem Event bauen, der garantiert + dokumentiert die Reihenfolge einhält. -
Hallo Marcel,
... nur bzgl. Deines Beispiels mit dem DemoHandler1->DemoHandler3 ... sei noch (zur Sicherheit für den OP) gesagt:
Der Umstand ist natürlich von der InvocationList unabhängig.
In einem normalen Thread kann es sein, dass eine Methode eines anderen
Threads ausgeführt wird bzw. dazwischenschiebt (ganz unabhängig von Events etc.)Ich sagte ja im ersten Posting auch:
"Es kann natürlich sein, dass sich eine Methode eines
anderen Threads dazwischenschiebt"
"... bzgl. dieses einen Threads ..."
Ansonsten hast Du meinen Quellcode ja noch mal anschaulich umschrieben.
ciao Frank -
Hallo Frank,
Der Umstand ist natürlich von der InvocationList unabhängig.
In einem normalen Thread kann es sein, dass eine Methode eines anderen
Threads ausgeführt wird bzw. dazwischenschiebt (ganz unabhängig von Events etc.)Nicht die InvocationList macht mir Sorgen. Denn Delegates sind immutable, d.h. nachdem sie einmal erstellt wurden, ändert sich ihre InvocationList nicht mehr. Add() und Remove() geben jeweils einen *neuen* Delegaten zurück, und die Bearbeitungs-Reihenfolge innerhalb des gegebenen Delegaten ist so gut wie unverrückbar. Worum es mir ging, war zu zeigen, dass diese Kombinatorik die den Delegaten und somit den Events innewohnt, Anwendungen, die auf eine ganz bestimmte Abfolge von Ereignissen basieren, anfällig macht gegenüber Threadingproblemen. Dass es überall, nicht nur bei den Events zu Racing-Bedingungen u.a. Threadingproblemen kommen kann, ist selbstverständlich. Bei kombinierbaren, immutablen Delegatlisten, die über die gleiche Referenz angesprochen werden können aber umso mehr.
Wäre das anders, würde man nicht immer wieder darauf hinweisen, dass es zu den best practices gehört, vor dem Zuweisungstest
if(DemoEvent != null){ DemoEvent(null, EventArgs.Empty); }
erstmal in eine temporäre Variable zu kopieren:
EventHandler handler = DemoEvent; if(handler != null) { handler(null, EventArgs.Empty); }
Gewusst wie: Veröffentlichen von Ereignissen, die den .NET Framework-Richtlinien entsprechen (C#-Programmierhandbuch)
http://msdn.microsoft.com/de-de/library/w369ty8x.aspxAnsonsten hast Du meinen Quellcode ja noch mal anschaulich umschrieben.
Danke ;-)
Gruß
Marcel -
Hallo Marcel,
> Bearbeitungs-Reihenfolge innerhalb des gegebenen
> Delegaten ist so gut wie unverrückbar.
ich weiß, was Du meinst, aber Du hast mich evtl. mißverstanden,
->im "Effekt" bei Threads eben nicht.
Tatsächlich können die Methoden dann in anderer Reihenfolge ausgeführt werden, weil vielleicht ganz simpel in einem anderen Thread die Methode aufgerufen wird - das war (von mir) gemeint. Deswegen wäre sowas in einem Multi-Thread-Szenario eh unsupportet und gefährlich.
Wichtig war mir zunächst nur, das klar wird, dass die synchrone Abarbeitung eines Threads schon durch einen anderen Thread abgebrochen werden kann, dass das erstmal unabhängig von Events bereits besteht und das Szenario "mehrere Threads" ja von meiner Seite zumindest nicht gemeint ist.> Kombinatorik die den Delegaten und somit den Events innewohnt,
ja, deswegen u.a. auch meine Aussage:
"Dennoch sollte das Bezugnehmen auf die verbindliche
Reihenfolge eher eine Seltenheit bleiben, nur, wenn es
einen spürbaren Vorteil bringt."
> if(DemoEvent != null){
ja, Standard. Ist jetzt aber schon etwas vom Thema weg.
ciao Frank