none
Abarbeitung von Events in Visual C# RRS feed

  • 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
    Dienstag, 24. August 2010 12:28

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
    Dienstag, 24. August 2010 12:54
  • 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
    Mittwoch, 25. August 2010 05:30
  • 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.

    Mittwoch, 25. August 2010 07:48
  • 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

     

    Mittwoch, 25. August 2010 08:25
  • 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

    Mittwoch, 25. August 2010 10:15
    Moderator
  • > 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.

    Mittwoch, 25. August 2010 11:50
  • 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
    Mittwoch, 25. August 2010 12:23
  • 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.aspx

    Ansonsten hast Du meinen Quellcode ja noch mal anschaulich umschrieben.

    Danke ;-)


    Gruß
    Marcel 

     

    Mittwoch, 25. August 2010 14:19
    Moderator
  • 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
    Mittwoch, 25. August 2010 14:51