Czy zdarzenia C # są synchroniczne?

104

Pytanie to składa się z dwóch części:

  1. Czy podniesienie zdarzenie zablokować wątek, czy też rozpocząć realizację EventHandlers asynchronicznie i idzie wątek kontynuuje w tym samym czasie?

  2. Czy poszczególne EventHandlers (zasubskrybowane zdarzenie) działają synchronicznie jeden po drugim, czy też działają asynchronicznie bez gwarancji, że inne nie działają w tym samym czasie?

Alexander Bird
źródło

Odpowiedzi:

37

Aby odpowiedzieć na Twoje pytania:

  1. Podniesienie zdarzenia powoduje zablokowanie wątku, jeśli wszystkie programy obsługi zdarzeń są zaimplementowane synchronicznie.
  2. Procedury obsługi zdarzeń są wykonywane sekwencyjnie, jeden po drugim, w kolejności, w jakiej zostały zasubskrybowane do zdarzenia.

Ja też byłem ciekawy wewnętrznego mechanizmu eventi związanych z nim operacji. Napisałem więc prosty program i przeglądałem ildasmjego implementację.

Krótka odpowiedź brzmi

  • nie ma operacji asynchronicznej zaangażowanej w subskrybowanie lub wywoływanie zdarzeń.
  • zdarzenie jest implementowane przy użyciu pola delegata zapasowego tego samego typu
  • subskrybowanie jest zakończone Delegate.Combine()
  • rezygnacja z subskrypcji jest zakończona Delegate.Remove()
  • Wywołanie odbywa się przez proste wywołanie ostatecznego połączonego delegata

Oto co zrobiłem. Program, którego użyłem:

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

Oto implementacja Foo:

wprowadź opis obrazu tutaj

Zauważ, że jest tam pole OnCall i wydarzenie OnCall . Pole OnCalljest oczywiście właściwością podkładową. I to jest po prostu Func<int, string>nic nadzwyczajnego.

Teraz interesujące części to:

  • add_OnCall(Func<int, string>)
  • remove_OnCall(Func<int, string>)
  • i jak OnCalljest wzywanyDo()

W jaki sposób realizowane jest subskrybowanie i anulowanie subskrypcji?

Oto skrócona add_OnCallimplementacja w CIL. Interesujące jest to, że używa go Delegate.Combinedo łączenia dwóch delegatów.

.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

Podobnie Delegate.Removejest używany w remove_OnCall.

Jak wywoływane jest wydarzenie?

Aby wywołać OnCallw Do(), po prostu wywołuje ostatecznego połączonego delegata po załadowaniu argumentu:

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

Jak dokładnie subskrybent subskrybuje wydarzenie?

I wreszcie, co Mainnie jest zaskakujące, subskrybowanie OnCallzdarzenia odbywa się poprzez wywołanie add_OnCallmetody w Fooinstancji.

KFL
źródło
3
Dobra robota!! Minęło tak dużo czasu, odkąd zadałem to pytanie. Jeśli możesz umieścić słownictwo na górze, które bezpośrednio odpowiada na moje dwuczęściowe pytanie (tj. „Odpowiedź nr 1 to nie; odpowiedź nr 2 to nie”), to uczynię to oficjalną odpowiedzią. Stawiam na Twój post jako wszystkie elementy odpowiadające na moje oryginalne pytania, ale ponieważ nie używam już C # (a inni pracownicy Google mogą być nowi w tych koncepcjach), dlatego proszę o słownictwo, które sprawia, że ​​odpowiedzi są oczywiste.
Alexander Bird
Dzięki @AlexanderBird, właśnie go zredagowałem, tak aby odpowiedzi były u góry.
KFL
@KFL, nadal nie jest jasne, miałem właśnie zostawić ten sam komentarz, co Alex. Proste „Tak, są synchroniczne”, byłoby pomocne
johnny 5
71

To jest ogólna odpowiedź i odzwierciedla domyślne zachowanie:

  1. Tak, blokuje wątek, jeśli metody subskrybujące zdarzenie nie są asynchroniczne.
  2. Są wykonywane jeden po drugim. Ma to inny skręt: jeśli jeden program obsługi zdarzeń zgłosi wyjątek, programy obsługi zdarzeń, które nie zostały jeszcze wykonane, nie zostaną wykonane.

Mimo to każda klasa, która dostarcza zdarzenia, może wybrać asynchroniczną implementację zdarzenia. IDesign udostępnia klasę o nazwie, EventsHelperktóra to upraszcza.

[Uwaga] ten link wymaga podania adresu e-mail w celu pobrania klasy EventsHelper. (Nie jestem w żaden sposób powiązany)

Daniel Hilgarth
źródło
Przeczytałem kilka postów na forum, z których dwa zaprzeczyły pierwszemu punktowi, nie podając jednak właściwej przyczyny. Nie wątpię w twoją odpowiedź (zgadza się z tym, czego do tej pory doświadczyłem) czy istnieje jakaś oficjalna dokumentacja dotycząca pierwszego punktu? Muszę być tego pewien, ale trudno mi znaleźć cokolwiek oficjalnego w tej konkretnej sprawie.
Adam LS
@ AdamL.S. Chodzi o to, jak zostanie nazwane wydarzenie. Więc to naprawdę zależy od klasy organizującej wydarzenie.
Daniel Hilgarth
14

Delegaci zasubskrybowani do zdarzenia są wywoływani synchronicznie w kolejności, w jakiej zostali dodani. Jeśli jeden z delegatów zgłosi wyjątek, następne nie zostaną wywołane.

Ponieważ zdarzenia są definiowane za pomocą delegatów multiemisji, możesz napisać własny mechanizm uruchamiania przy użyciu

Delegate.GetInvocationList();

i wywoływanie delegatów asynchronicznie;

Vladimir
źródło
12

Zdarzenia to po prostu tablice delegatów. Dopóki wywołanie pełnomocnika jest synchroniczne, zdarzenia są również synchroniczne.

Andrey Agibalov
źródło
3

Zdarzenia w C # są uruchamiane synchronicznie (w obu przypadkach), o ile nie uruchomisz ręcznie drugiego wątku.

Doc Brown
źródło
A co z korzystaniem z obsługi zdarzeń asynchronicznych? Czy będzie wtedy działać w innym wątku? Słyszałem o „Async all the way”, ale wygląda na to, że programy obsługi zdarzeń asynchronicznych mają swój własny wątek? Nie rozumiem: / Czy możesz mnie oświecić?
Skrzydłowy Sendon
3

Wydarzenia są synchroniczne. Dlatego cykl życia wydarzenia działa tak, jak to robi. Inits następują przed ładowaniem, ładowanie odbywa się przed renderowaniem itp.

Jeśli dla zdarzenia nie zostanie określona żadna procedura obsługi, cykl po prostu przepływa. Jeśli określono więcej niż jeden program obsługi, będą one wywoływane po kolei i jeden nie będzie mógł kontynuować, dopóki drugi nie zostanie całkowicie zakończony.

Nawet wywołania asynchroniczne są do pewnego stopnia synchroniczne. Niemożliwe byłoby wywołanie końca przed zakończeniem początku.

Joel Etherton
źródło