¿Los eventos de C # son sincrónicos?

104

Hay dos partes en esta pregunta:

  1. ¿La activación de un evento bloquea el hilo o inicia la ejecución de EventHandlers de forma asincrónica y el hilo continúa al mismo tiempo?

  2. ¿Los EventHandlers individuales (suscritos al evento) se ejecutan sincrónicamente uno tras otro, o se ejecutan de forma asincrónica sin garantía de que otros no se estén ejecutando al mismo tiempo?

Alexander Bird
fuente

Respuestas:

37

Para responder tu pregunta:

  1. La generación de un evento bloquea el hilo si todos los controladores de eventos se implementan sincrónicamente.
  2. Los controladores de eventos se ejecutan secuencialmente, uno tras otro, en el orden en que están suscritos al evento.

Yo también tenía curiosidad por el mecanismo interno de eventy sus operaciones relacionadas. Así que escribí un programa simple y solía ildasmhurgar en su implementación.

La respuesta corta es

  • no hay ninguna operación asincrónica involucrada en la suscripción o invocación de eventos.
  • El evento se implementa con un campo de delegado de respaldo del mismo tipo de delegado.
  • la suscripción se realiza con Delegate.Combine()
  • darse de baja se hace con Delegate.Remove()
  • La invocación se realiza simplemente invocando al delegado combinado final

Esto es lo que hice. El programa que utilicé:

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();
    }
}

Aquí está la implementación de Foo:

ingrese la descripción de la imagen aquí

Tenga en cuenta que hay un campo OnCall y un evento OnCall . El campo OnCalles obviamente la propiedad de respaldo. Y es simplemente un Func<int, string>, nada lujoso aquí.

Ahora las partes interesantes son:

  • add_OnCall(Func<int, string>)
  • remove_OnCall(Func<int, string>)
  • y como OnCallse invoca enDo()

¿Cómo se implementa la suscripción y cancelación de suscripción?

Aquí está la add_OnCallimplementación abreviada en CIL. La parte interesante es que se utiliza Delegate.Combinepara concatenar dos delegados.

.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

Asimismo, Delegate.Removese utiliza en remove_OnCall.

¿Cómo se invoca un evento?

Para invocar OnCallen Do(), simplemente llama al delegado concatenada final después de cargar el arg:

IL_0026:  callvirt   instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)

¿Cómo se suscribe exactamente un suscriptor a un evento?

Y finalmente, Mainno es sorprendente que la suscripción al OnCallevento se realice llamando al add_OnCallmétodo en la Fooinstancia.

KFL
fuente
3
¡¡Bien hecho!! Ha pasado tanto tiempo desde que hice esta pregunta. Si puede poner verborrea en la parte superior que responde directamente a mi pregunta de dos partes (es decir, "la respuesta n. ° 1 es no; la respuesta n. ° 2 es no"), haré que esta sea la respuesta oficial. Apuesto a que su publicación es todas las piezas para responder a mis preguntas originales, pero como ya no uso C # (y otros usuarios de Google pueden ser nuevos en estos conceptos), es por eso que estoy pidiendo verborrea que hace que las respuestas sean obvias.
Alexander Bird
Gracias @AlexanderBird, solo lo edité para poner las respuestas en la parte superior.
KFL
@KFL, todavía no está claro, estaba a punto de dejar el mismo comentario que Alex. Un simple "Sí, son sincrónicos", sería útil
johnny 5
71

Esta es una respuesta general y refleja el comportamiento predeterminado:

  1. Sí, bloquea el hilo, si los métodos que se suscriben al evento no son asincrónicos.
  2. Se ejecutan uno tras otro. Esto tiene otro giro: si un controlador de eventos genera una excepción, los controladores de eventos que aún no se hayan ejecutado no se ejecutarán.

Dicho esto, cada clase que proporciona eventos puede optar por implementar su evento de forma asincrónica. IDesign proporciona una clase llamada EventsHelperque simplifica esto.

[Nota] este enlace requiere que proporciones una dirección de correo electrónico para descargar la clase EventsHelper. (No estoy afiliado de ninguna manera)

Daniel Hilgarth
fuente
He leído algunas publicaciones en el foro, de las cuales dos contradicen el primer punto, sin dar una razón adecuada. No dudo de tu respuesta (coincide con lo que he experimentado hasta ahora) ¿Existe alguna documentación oficial sobre el primer punto? Necesito estar seguro de esto, pero tengo dificultades para encontrar algo oficial sobre este asunto exacto.
Adam LS
@ AdamL.S. Es cuestión de cómo se llame al evento. Así que realmente depende de la clase que imparta el evento.
Daniel Hilgarth
14

Los delegados suscritos al evento se invocan sincrónicamente en el orden en que se agregaron. Si uno de los delegados lanza una excepción, no se llamará a los siguientes.

Dado que los eventos se definen con delegados de multidifusión, puede escribir su propio mecanismo de activación utilizando

Delegate.GetInvocationList();

e invocar a los delegados de forma asincrónica;

Vladimir
fuente
12

Los eventos son simplemente conjuntos de delegados. Siempre que la llamada de delegado sea síncrona, los eventos también lo son.

Andrey Agibalov
fuente
3

Los eventos en C # se ejecutan sincrónicamente (en ambos casos), siempre y cuando no inicie un segundo subproceso manualmente.

Doc Brown
fuente
¿Qué pasa si uso un controlador de eventos asíncrono? ¿Luego se ejecutará en otro hilo? He oído hablar de "Async hasta el final", pero parece que los controladores de eventos async tienen su propio hilo. No entiendo: / ¿Puedes iluminarme?
Winger Sendon
3

Los eventos son sincrónicos. Es por eso que el ciclo de vida del evento funciona como lo hace. Los inicios ocurren antes de las cargas, las cargas ocurren antes de los renders, etc.

Si no se especifica un controlador para un evento, el ciclo simplemente se desarrolla. Si se especifica más de un controlador, se llamarán en orden y uno no puede continuar hasta que el otro haya terminado por completo.

Incluso las llamadas asincrónicas son sincrónicas hasta cierto punto. Sería imposible llamar al final antes de que se complete el comienzo.

Joel Etherton
fuente