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.
Ceci est une réponse générale et reflète le comportement par défaut:
Cela dit, chaque classe qui fournit des événements peut choisir d'implémenter son événement de manière asynchrone. IDesign fournit une classe appelée
EventsHelper
qui simplifie cela.[Note] ce lien vous oblige à fournir une adresse e-mail pour télécharger la classe EventsHelper. (Je ne suis en aucun cas affilié)
la source
Les délégués abonnés à l'événement sont appelés de manière synchrone dans l'ordre dans lequel ils ont été ajoutés. Si l'un des délégués lève une exception, les suivants ne seront pas appelés.
Étant donné que les événements sont définis avec des délégués multicast, vous pouvez écrire votre propre mécanisme de déclenchement en utilisant
et appeler les délégués de manière asynchrone;
la source
Les événements ne sont que des tableaux de délégués. Tant que l'appel du délégué est synchrone, les événements sont également synchrones.
la source
En général, les événements sont synchrones. Cependant, il existe quelques exceptions, telles que l'
System.Timers.Timer.Elapsed
événement déclenché sur unThreadPool
thread siSyncronisingObject
est nul.Documents: http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx
la source
Les événements en C # s'exécutent de manière synchrone (dans les deux cas), tant que vous ne démarrez pas un deuxième thread manuellement.
la source
Les événements sont synchrones. C'est pourquoi le cycle de vie des événements fonctionne comme il le fait. Les inits se produisent avant les chargements, les chargements se produisent avant les rendus, etc.
Si aucun gestionnaire n'est spécifié pour un événement, le cycle éclate simplement. Si plus d'un gestionnaire est spécifié, ils seront appelés dans l'ordre et l'un ne peut pas continuer tant que l'autre n'est pas complètement terminé.
Même les appels asynchrones sont synchrones dans une certaine mesure. Il serait impossible d'appeler la fin avant que le début ne soit terminé.
la source