Sind C # -Ereignisse synchron?

104

Diese Frage besteht aus zwei Teilen:

  1. Hat Anheben ein Ereignis den Thread blockieren, oder startet die Ausführung von Eventhandler asynchron und der Faden weiter zum gleichen Zeit geht?

  2. Werden die einzelnen EventHandler (die das Ereignis abonniert haben) nacheinander synchron ausgeführt oder werden sie asynchron ausgeführt, ohne dass garantiert wird, dass andere nicht gleichzeitig ausgeführt werden?

Alexander Bird
quelle

Antworten:

37

So beantworten Sie Ihre Fragen:

  1. Das Auslösen eines Ereignisses blockiert den Thread, wenn die Ereignishandler alle synchron implementiert sind.
  2. Die Ereignishandler werden nacheinander in der Reihenfolge ausgeführt, in der sie das Ereignis abonniert haben.

Auch ich war neugierig auf den internen Mechanismus eventund die damit verbundenen Operationen. Also schrieb ich ein einfaches Programm und ildasmstöberte in seiner Implementierung herum.

Die kurze Antwort lautet

  • Es gibt keine asynchrone Operation zum Abonnieren oder Aufrufen der Ereignisse.
  • Das Ereignis wird mit einem Hintergrunddelegatenfeld desselben Delegatentyps implementiert
  • Das Abonnieren ist abgeschlossen mit Delegate.Combine()
  • Das Abbestellen erfolgt mit Delegate.Remove()
  • Das Aufrufen erfolgt durch einfaches Aufrufen des endgültigen kombinierten Delegaten

Folgendes habe ich getan. Das Programm, das ich verwendet habe:

public class Foo
{
    // cool, it can return a value! which value it returns if there're multiple 
    // subscribers? answer (by trying): the last subscriber.
    public event Func<int, string> OnCall;
    private int val = 1;

    public void Do()
    {
        if (OnCall != null) 
        {
            var res = OnCall(val++);
            Console.WriteLine($"publisher got back a {res}");
        }
    }
}

public class Program
{
    static void Main(string[] args)
    {
        var foo = new Foo();

        foo.OnCall += i =>
        {
            Console.WriteLine($"sub2: I've got a {i}");
            return "sub2";
        };

        foo.OnCall += i =>
        {
            Console.WriteLine($"sub1: I've got a {i}");
            return "sub1";
        };

        foo.Do();
        foo.Do();
    }
}

Hier ist die Implementierung von Foo:

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass es ein Feld OnCall und ein Ereignis gibt OnCall . Das Feld OnCallist offensichtlich die Backing-Eigenschaft. Und es ist nur ein Func<int, string>, nichts Besonderes hier.

Nun sind die interessanten Teile:

  • add_OnCall(Func<int, string>)
  • remove_OnCall(Func<int, string>)
  • und wie OnCallwird in aufgerufenDo()

Wie wird das Abonnieren und Abbestellen implementiert?

Hier ist die abgekürzte add_OnCallImplementierung in CIL. Der interessante Teil ist, dass Delegate.Combinezwei Delegaten verkettet werden.

.method public hidebysig specialname instance void 
        add_OnCall(class [mscorlib]System.Func`2<int32,string> 'value') cil managed
{
  // ...
  .locals init (class [mscorlib]System.Func`2<int32,string> V_0,
           class [mscorlib]System.Func`2<int32,string> V_1,
           class [mscorlib]System.Func`2<int32,string> V_2)
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [mscorlib]System.Func`2<int32,string> ConsoleApp1.Foo::OnCall
  // ...
  IL_000b:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
                                                                                          class [mscorlib]System.Delegate)
  // ...
} // end of method Foo::add_OnCall

Ebenso Delegate.Removewird in verwendet remove_OnCall.

Wie wird ein Ereignis aufgerufen?

Zum Aufrufen OnCallin Do(), ruft er einfach die letzte verkettete Delegierten nach dem ARG - Laden:

IL_0026:  callvirt   instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)

Wie genau abonniert ein Abonnent eine Veranstaltung?

Und schließlich Mainerfolgt das Abonnieren des OnCallEreignisses , nicht überraschend, durch Aufrufen der add_OnCallMethode für die FooInstanz.

KFL
quelle
3
Gut gemacht!! Es ist so lange her, dass ich diese Frage gestellt habe. Wenn Sie oben eine Sprache einfügen können, die meine zweiteilige Frage direkt beantwortet (dh "Antwort Nr. 1 ist nein; Antwort Nr. 2 ist nein"), dann mache ich dies zur offiziellen Antwort. Ich wette auf Ihren Beitrag als alle Teile, um meine ursprünglichen Fragen zu beantworten, aber da ich kein C # mehr verwende (und andere Googler möglicherweise neu in diesen Konzepten sind), bitte ich um eine Redewendung, die die Antworten offensichtlich macht.
Alexander Bird
Danke @AlexanderBird, habe es gerade bearbeitet, um die Antworten oben zu platzieren.
KFL
@KFL, es ist immer noch unklar, ich wollte gerade den gleichen Kommentar wie Alex hinterlassen. Ein einfaches "Ja, sie sind synchron" wäre hilfreich
Johnny 5.
71

Dies ist eine allgemeine Antwort und spiegelt das Standardverhalten wider:

  1. Ja, es blockiert den Thread, wenn die Methoden, die das Ereignis abonnieren, nicht asynchron sind.
  2. Sie werden nacheinander ausgeführt. Dies hat eine andere Wendung: Wenn ein Ereignishandler eine Ausnahme auslöst, werden die noch nicht ausgeführten Ereignishandler nicht ausgeführt.

Allerdings kann jede Klasse, die Ereignisse bereitstellt, ihr Ereignis asynchron implementieren. IDesign bietet eine Klasse namens EventsHelper, die dies vereinfacht.

[Hinweis] Für diesen Link müssen Sie eine E-Mail-Adresse angeben, um die EventsHelper-Klasse herunterladen zu können. (Ich bin in keiner Weise verbunden)

Daniel Hilgarth
quelle
Ich habe einige Forenbeiträge gelesen, von denen zwei dem ersten Punkt widersprochen haben, ohne einen angemessenen Grund anzugeben. Ich bezweifle nicht Ihre Antwort (es stimmt mit dem überein, was ich bisher erlebt habe). Gibt es eine offizielle Dokumentation zum ersten Punkt? Ich muss mir sicher sein, aber ich habe Schwierigkeiten, etwas offizielles in dieser Angelegenheit zu finden.
Adam LS
@ AdamL.S. Es kommt darauf an, wie das Ereignis aufgerufen wird. Es kommt also wirklich auf die Klasse an, die die Veranstaltung anbietet.
Daniel Hilgarth
14

Die für die Veranstaltung abonnierten Delegierten werden synchron in der Reihenfolge aufgerufen, in der sie hinzugefügt wurden. Wenn einer der Delegierten eine Ausnahme auslöst, werden die folgenden nicht aufgerufen.

Da Ereignisse mit Multicast-Delegaten definiert werden, können Sie mithilfe von Ihren eigenen Auslösemechanismus schreiben

Delegate.GetInvocationList();

und asynchrones Aufrufen der Delegierten;

Vladimir
quelle
12

Ereignisse sind nur Anordnungen von Delegierten. Solange der Delegatenanruf synchron ist, sind auch Ereignisse synchron.

Andrey Agibalov
quelle
3

Ereignisse in C # werden synchron ausgeführt (in beiden Fällen), solange Sie keinen zweiten Thread manuell starten.

Doc Brown
quelle
Was ist mit einem asynchronen Ereignishandler? Wird es dann in einem anderen Thread ausgeführt? Ich habe den ganzen Weg von "Async" gehört, aber es scheint, als hätten Async-Ereignishandler einen eigenen Thread? Ich verstehe nicht: / Kannst du mich bitte aufklären?
Flügelspieler Sendon
3

Ereignisse sind synchron. Aus diesem Grund funktioniert der Ereignislebenszyklus so, wie er funktioniert. Inits passieren vor dem Laden, Ladungen passieren vor dem Rendern usw.

Wenn für ein Ereignis kein Handler angegeben ist, wird der Zyklus nur durchlaufen. Wenn mehr als ein Handler angegeben ist, werden sie der Reihe nach aufgerufen und einer kann erst fortgesetzt werden, wenn der andere vollständig fertig ist.

Selbst asynchrone Anrufe sind bis zu einem gewissen Grad synchron. Es ist unmöglich, das Ende aufzurufen, bevor der Anfang abgeschlossen ist.

Joel Etherton
quelle