Pour répondre à tes questions:
- Le déclenchement d'un événement bloque le thread si les gestionnaires d'événements sont tous implémentés de manière synchrone.
- Les gestionnaires d'événements sont exécutés séquentiellement, les uns après les autres, dans l'ordre dans lequel ils sont abonnés à l'événement.
J'étais moi aussi curieux de connaître le mécanisme interne de event
et ses opérations connexes. J'ai donc écrit un programme simple et utilisé ildasm
pour fouiller autour de son implémentation.
La réponse courte est
- il n'y a aucune opération asynchrone impliquée dans l'abonnement ou l'appel des événements.
- l'événement est implémenté avec un champ de délégué de support du même type de délégué
- l'abonnement se fait avec
Delegate.Combine()
- la désinscription se fait avec
Delegate.Remove()
- L'appel se fait simplement en invoquant le délégué combiné final
Voici ce que j'ai fait. Le programme que j'ai utilisé:
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();
}
}
Voici l'implémentation de Foo:
Notez qu'il existe un champ OnCall
et un événement OnCall
. Le champ OnCall
est évidemment la propriété de support. Et c'est simplement un Func<int, string>
, rien d'extraordinaire ici.
Maintenant, les parties intéressantes sont:
add_OnCall(Func<int, string>)
remove_OnCall(Func<int, string>)
- et comment
OnCall
est invoqué dansDo()
Comment l'abonnement et la désinscription sont-ils mis en œuvre?
Voici l' add_OnCall
implémentation abrégée dans CIL. La partie intéressante est qu'il utilise Delegate.Combine
pour concaténer deux délégués.
.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
De même, Delegate.Remove
est utilisé dans remove_OnCall
.
Comment un événement est-il invoqué?
Pour invoquer OnCall
in Do()
, il appelle simplement le délégué concaténé final après le chargement de l'arg:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
Comment exactement un abonné s'abonne-t-il à un événement?
Et enfin, Main
sans surprise, l'inscription à l' OnCall
événement se fait en appelant add_OnCall
method sur l' Foo
instance.