Gli eventi C # sono sincroni?


104

Ci sono due parti in questa domanda:

  1. La generazione di un evento blocca il thread o avvia l'esecuzione di EventHandlers in modo asincrono e il thread prosegue contemporaneamente?

  2. I singoli EventHandlers (iscritti all'evento) vengono eseguiti in modo sincrono uno dopo l'altro o vengono eseguiti in modo asincrono senza alcuna garanzia che gli altri non siano in esecuzione contemporaneamente?

Risposte:


37

Per rispondere alle tue domande:

  1. La generazione di un evento blocca il thread se i gestori di eventi sono tutti implementati in modo sincrono.
  2. I gestori di eventi vengono eseguiti in sequenza, uno dopo l'altro, nell'ordine in cui sono iscritti all'evento.

Anch'io ero curioso del meccanismo interno di evente delle sue operazioni correlate. Quindi ho scritto un semplice programma e ne ho ildasmcurati l'implementazione.

La risposta breve è

  • non ci sono operazioni asincrone coinvolte nella sottoscrizione o nel richiamo degli eventi.
  • L'evento viene implementato con un campo delegato di supporto dello stesso tipo di delegato
  • l'iscrizione è terminata con Delegate.Combine()
  • l'annullamento dell'iscrizione è fatto con Delegate.Remove()
  • La chiamata viene eseguita semplicemente invocando il delegato combinato finale

Ecco cosa ho fatto. Il programma che ho usato:

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

Ecco l'implementazione di Foo:

inserisci qui la descrizione dell'immagine

Nota che sono presenti un campo OnCall e un evento OnCall . Il campo OnCallè ovviamente la proprietà di supporto. Ed è semplicemente un fileFunc<int, string> , niente di speciale qui.

Ora le parti interessanti sono:

  • add_OnCall(Func<int, string>)
  • remove_OnCall(Func<int, string>)
  • e come OnCallviene invocato inDo()

Come vengono implementate la sottoscrizione e l'annullamento della sottoscrizione?

Ecco l' add_OnCallimplementazione abbreviata in CIL. La parte interessante è che utilizza Delegate.Combineper concatenare due delegati.

.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

Allo stesso modo, Delegate.Removeviene utilizzato in remove_OnCall.

Come viene invocato un evento?

Per richiamare OnCallin Do(), si chiama semplicemente il delegato concatenato finale dopo aver caricato l'arg:

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

In che modo esattamente un abbonato si iscrive a un evento?

Infine, Mainnon sorprendentemente, la sottoscrizione OnCallall'evento viene eseguita chiamando il add_OnCallmetodo Foosull'istanza.


3
Molto bene!! È passato così tanto tempo da quando ho fatto questa domanda. Se puoi mettere la verbosità in alto che risponde direttamente alla mia domanda in due parti (cioè, "la risposta n. 1 è no; la risposta n. 2 è no"), allora renderò questa la risposta ufficiale. Scommetto che il tuo post contiene tutti i pezzi per rispondere alle mie domande originali, ma dal momento che non uso più C # (e altri googler potrebbero essere nuovi a questi concetti) è per questo che chiedo verbosità che rende le risposte ovvie.
Alexander Bird

Grazie @AlexanderBird, l'ho appena modificato per mettere le risposte in alto.
KFL

@KFL, non è ancora chiaro, stavo per lasciare lo stesso commento di Alex. Un semplice "Sì, sono sincroni", sarebbe utile
johnny 5

71

Questa è una risposta generale e riflette il comportamento predefinito:

  1. Sì, blocca il thread, se i metodi di sottoscrizione all'evento non sono asincroni.
  2. Vengono eseguiti uno dopo l'altro. Questo ha un'altra svolta: se un gestore di eventi genera un'eccezione, i gestori di eventi non ancora eseguiti non verranno eseguiti.

Detto questo, ogni classe che fornisce eventi può scegliere di implementare il proprio evento in modo asincrono. IDesign fornisce una classe chiamata EventsHelperche semplifica questo.

[Nota] questo collegamento richiede di fornire un indirizzo e-mail per scaricare la classe EventsHelper. (Non sono affiliato in alcun modo)


Ho letto alcuni post del forum, da cui due hanno contraddetto il primo punto, pur non fornendo una ragione adeguata. Non metto in dubbio la tua risposta, (corrisponde a quello che ho sperimentato finora) c'è qualche documentazione ufficiale sul primo punto? Devo essere certo di questo, ma ho difficoltà a trovare qualcosa di ufficiale su questo argomento.
Adam LS

@ AdamL.S. È una questione di come viene chiamato l'evento. Quindi dipende davvero dalla classe che fornisce l'evento.
Daniel Hilgarth

14

I delegati iscritti all'evento vengono richiamati in modo sincrono nell'ordine in cui sono stati aggiunti. Se uno dei delegati genera un'eccezione, quelli seguenti non verranno chiamati.

Poiché gli eventi vengono definiti con delegati multicast, è possibile scrivere il proprio meccanismo di attivazione utilizzando

Delegate.GetInvocationList();

e invocare i delegati in modo asincrono;


12

Gli eventi sono solo array di delegati. Finché la chiamata del delegato è sincrona, anche gli eventi sono sincroni.



3

Gli eventi in C # vengono eseguiti in modo sincrono (in entrambi i casi), a condizione che non si avvii manualmente un secondo thread.


Che dire se uso un gestore di eventi asincrono? Verrà quindi eseguito in un altro thread? Ho sentito parlare di "Async all the way", ma sembra che i gestori di eventi asincroni abbiano il loro thread? Non capisco: / Puoi illuminarmi per favore?
Winger Sendon

3

Gli eventi sono sincroni. Questo è il motivo per cui il ciclo di vita degli eventi funziona in questo modo. Gli inizi avvengono prima dei caricamenti, i carichi avvengono prima dei rendering, ecc.

Se non viene specificato alcun gestore per un evento, il ciclo si esaurisce. Se viene specificato più di un gestore, verranno chiamati in ordine e uno non potrà continuare finché l'altro non sarà completamente terminato.

Anche le chiamate asincrone sono sincrone in una certa misura. Sarebbe impossibile chiamare la fine prima che l'inizio sia completato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.