Aby odpowiedzieć na Twoje pytania:
- Podniesienie zdarzenia powoduje zablokowanie wątku, jeśli wszystkie programy obsługi zdarzeń są zaimplementowane synchronicznie.
- 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 event
i związanych z nim operacji. Napisałem więc prosty program i przeglądałem ildasm
jego 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:
Zauważ, że jest tam pole OnCall
i wydarzenie OnCall
. Pole OnCall
jest 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
OnCall
jest wzywanyDo()
W jaki sposób realizowane jest subskrybowanie i anulowanie subskrypcji?
Oto skrócona add_OnCall
implementacja w CIL. Interesujące jest to, że używa go Delegate.Combine
do łą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.Remove
jest używany w remove_OnCall
.
Jak wywoływane jest wydarzenie?
Aby wywołać OnCall
w 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 Main
nie jest zaskakujące, subskrybowanie OnCall
zdarzenia odbywa się poprzez wywołanie add_OnCall
metody w Foo
instancji.
To jest ogólna odpowiedź i odzwierciedla domyślne zachowanie:
Mimo to każda klasa, która dostarcza zdarzenia, może wybrać asynchroniczną implementację zdarzenia. IDesign udostępnia klasę o nazwie,
EventsHelper
która to upraszcza.[Uwaga] ten link wymaga podania adresu e-mail w celu pobrania klasy EventsHelper. (Nie jestem w żaden sposób powiązany)
źródło
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
i wywoływanie delegatów asynchronicznie;
źródło
Zdarzenia to po prostu tablice delegatów. Dopóki wywołanie pełnomocnika jest synchroniczne, zdarzenia są również synchroniczne.
źródło
Ogólnie zdarzenia są synchroniczne. Istnieją jednak wyjątki, takie jak
System.Timers.Timer.Elapsed
zdarzenie zgłaszane wThreadPool
wątku, jeśliSyncronisingObject
jest null.Dokumenty: http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx
źródło
Zdarzenia w C # są uruchamiane synchronicznie (w obu przypadkach), o ile nie uruchomisz ręcznie drugiego wątku.
źródło
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.
źródło