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.