Cette question comporte deux parties:
augmentation un événement bloque-t-il le thread, ou démarre-t-il l'exécution des EventHandlers de manière asynchrone et le thread continue en même temps?
Les EventHandlers individuels (abonnés à l'événement) sont-ils exécutés de manière synchrone les uns après les autres, ou sont-ils exécutés de manière asynchrone sans garantie que d'autres ne s'exécutent pas en même temps?
Pour répondre à vos questions:
Moi aussi j'étais 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 son implémentation.
La réponse courte est
Delegate.Combine()
Delegate.Remove()
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 ce n'est qu'un Func<int, string>
, Rien d'extraordinaire ici.
Maintenant, les parties intéressantes sont:
add_OnCall(Func<int, string>)
remove_OnCall(Func<int, string>)
OnCall
est invoqué dans Do()
Voici l'implémentation abrégée de add_OnCall
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
.
Pour appeler OnCall
dans Do()
, il appelle simplement le délégué concaténé final après avoir chargé l'argument:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
Et enfin, dans Main
, sans surprise, la souscription à l'événement OnCall
se fait en appelant la méthode add_OnCall
Sur l'instance Foo
.
Il s'agit d'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 affilié en aucune façon)
Les délégués abonnés à l'événement sont invoqué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, ceux qui suivent ne seront pas seront appelés.
Étant donné que les événements sont définis avec des délégués de multidiffusion, vous pouvez écrire votre propre mécanisme de déclenchement à l'aide de
Delegate.GetInvocationList();
et invoquer les délégués de manière asynchrone;
Les événements ne sont que des tableaux de délégués. Tant que l'appel délégué est synchrone, les événements sont également synchrones.
En général, les événements sont synchrones. Cependant, il existe quelques exceptions, telles que System.Timers.Timer.Elapsed
événement déclenché sur un thread ThreadPool
si SyncronisingObject
est nul.
Documents: http://msdn.Microsoft.com/en-us/library/system.timers.timer.elapsed.aspx
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.
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 charges, les charges se produisent avant les rendus, etc.
Si aucun gestionnaire n'est spécifié pour un événement, le cycle se déclenche simplement. Si plusieurs gestionnaires sont spécifiés, ils seront appelés dans l'ordre et l'un ne pourra pas continuer tant que l'autre ne sera pas complètement terminé.
Même les appels asynchrones sont synchrones dans une certaine mesure. Il serait impossible d'appeler la fin avant la fin du début.