web-dev-qa-db-fra.com

les événements C # sont-ils synchrones?

Cette question comporte deux parties:

  1. 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?

  2. 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?

95
Alexander Bird

Pour répondre à vos questions:

  1. Déclencher un événement bloque le thread si les gestionnaires d'événements sont tous implémentés de manière synchrone.
  2. Les gestionnaires d'événements sont exécutés séquentiellement, l'un après l'autre, dans l'ordre dans lequel ils sont abonnés à l'événement.

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

  • aucune opération asynchrone n'est impliquée dans l'abonnement ou l'appel des événements.
  • l'événement est implémenté avec un champ délégué de sauvegarde du même type de délégué
  • l'inscription se fait avec Delegate.Combine()
  • la désinscription se fait avec Delegate.Remove()
  • L'invocation 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:

enter image description here

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>)
  • et comment OnCall est invoqué dans Do()

Comment la souscription et la désinscription sont-elles mises en œuvre?

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.

Comment un événement est-il invoqué?

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)

Comment un abonné souscrit-il exactement à un événement?

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.

26
KFL

Il s'agit d'une réponse générale et reflète le comportement par défaut:

  1. Oui, il bloque le thread, si les méthodes de souscription à l'événement ne sont pas asynchrones.
  2. Ils sont exécutés les uns après les autres. Cela a une autre tournure: si un gestionnaire d'événements lève une exception, les gestionnaires d'événements non encore exécutés ne seront pas exécutés.

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)

70
Daniel Hilgarth

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;

14
Vladimir

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.

12
Andrey Agibalov

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

7
Jamiec

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.

3
Doc Brown

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.

3
Joel Etherton