Os eventos C # são síncronos?

104

Esta questão tem duas partes:

  1. Será que levantar um evento bloquear o fio, ou ele começar a execução de EventHandlers de forma assíncrona e o fio vai continuar ao mesmo tempo?

  2. Os EventHandlers individuais (inscritos no evento) são executados de forma síncrona um após o outro ou são executados de forma assíncrona, sem garantia de que outros não estejam executando ao mesmo tempo?

Alexander Bird
fonte

Respostas:

37

Para responder às suas perguntas:

  1. Gerar um evento bloqueia o encadeamento se os manipuladores de eventos forem todos implementados de forma síncrona.
  2. Os tratadores de eventos são executados sequencialmente, um após o outro, na ordem em que são inscritos no evento.

Eu também estava curioso sobre o mecanismo interno evente suas operações relacionadas. Então, escrevi um programa simples e costumava ildasmvasculhar sua implementação.

A resposta curta é

  • não há operação assíncrona envolvida na inscrição ou invocação dos eventos.
  • evento é implementado com um campo de delegado de apoio do mesmo tipo de delegado
  • a inscrição é feita com Delegate.Combine()
  • o cancelamento da inscrição é feito com Delegate.Remove()
  • A invocação é feita simplesmente invocando o delegado combinado final

Aqui está o que eu fiz. O programa que usei:

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

Aqui está a implementação de Foo:

insira a descrição da imagem aqui

Observe que há um campo OnCall e um evento OnCall . O campo OnCallé obviamente a propriedade de apoio. E é apenas um Func<int, string>, nada sofisticado aqui.

Agora, as partes interessantes são:

  • add_OnCall(Func<int, string>)
  • remove_OnCall(Func<int, string>)
  • e como OnCallé invocado emDo()

Como a inscrição e o cancelamento da inscrição são implementados?

Aqui está a add_OnCallimplementação abreviada em CIL. A parte interessante é que ele usa Delegate.Combinepara concatenar dois 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

Da mesma forma, Delegate.Removeé usado em remove_OnCall.

Como um evento é invocado?

Para chamar OnCallem Do(), ele simplesmente chama o delegado concatenado final depois de carregar o arg:

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

Como exatamente um assinante se inscreve em um evento?

E, finalmente, Mainnão é de surpreender que a inscrição no OnCallevento seja feita chamando o add_OnCallmétodo na Fooinstância.

KFL
fonte
3
Bem feito!! Já se passou muito tempo desde que fiz esta pergunta. Se você puder colocar um palavreado no topo que responde diretamente à minha pergunta de duas partes (ou seja, "a resposta nº 1 é não; a resposta nº 2 é não"), então farei esta a resposta oficial. Estou apostando na sua postagem como todas as peças para responder às minhas perguntas originais, mas como não uso mais C # (e outros googlers podem ser novos para esses conceitos), é por isso que estou pedindo verborragia que torna as respostas óbvias.
Alexander Bird
Obrigado @AlexanderBird, acabei de editá-lo para colocar as respostas no topo.
KFL
@KFL, ainda não está claro, eu estava prestes a deixar o mesmo comentário que Alex. Um simples "Sim, eles são síncronos" seria útil
johnny 5 de
71

Esta é uma resposta geral e reflete o comportamento padrão:

  1. Sim, ele bloqueia o encadeamento, se os métodos de inscrição no evento não forem assíncronos.
  2. Eles são executados um após o outro. Isso tem outra peculiaridade: se um manipulador de eventos lançar uma exceção, os manipuladores de eventos ainda não executados não serão executados.

Dito isso, cada classe que fornece eventos pode escolher implementar seu evento de forma assíncrona. IDesign fornece uma classe chamada EventsHelperque simplifica isso.

[Nota] este link requer que você forneça um endereço de e-mail para baixar a classe EventsHelper. (Não sou afiliado de forma alguma)

Daniel Hilgarth
fonte
Eu li algumas postagens do fórum, das quais duas contradizem o primeiro ponto, embora não forneçam um motivo adequado. Não duvido da sua resposta (corresponde ao que experimentei até agora). Existe alguma documentação oficial sobre o primeiro ponto? Preciso ter certeza disso, mas tenho dificuldade em encontrar algo oficial sobre esse assunto exato.
Adam LS
@ AdamL.S. É uma questão de como o evento é chamado. Portanto, realmente depende da classe que organiza o evento.
Daniel Hilgarth
14

Os delegados inscritos no evento são chamados de forma síncrona na ordem em que foram adicionados. Se um dos delegados lançar uma exceção, os seguintes não serão chamados.

Uma vez que os eventos são definidos com delegados multicast, você pode escrever seu próprio mecanismo de disparo usando

Delegate.GetInvocationList();

e invocar os delegados de forma assíncrona;

Vladimir
fonte
12

Os eventos são apenas matrizes de delegados. Desde que a chamada de delegado seja síncrona, os eventos também são síncronos.

Andrey Agibalov
fonte
3

Os eventos em C # são executados de forma síncrona (em ambos os casos), desde que você não inicie um segundo thread manualmente.

Doc Brown
fonte
Que tal eu usar um manipulador de eventos assíncrono? Será executado em outro segmento? Eu ouvi falar sobre "Async all way", mas parece que os manipuladores de eventos async têm seu próprio thread. Eu não entendo: / Você pode me esclarecer?
Winger Sendon
3

Os eventos são síncronos. É por isso que o ciclo de vida do evento funciona da maneira que funciona. Os inits acontecem antes dos carregamentos, os carregamentos acontecem antes dos renderizações etc.

Se nenhum manipulador for especificado para um evento, o ciclo simplesmente continua. Se mais de um manipulador for especificado, eles serão chamados em ordem e um não poderá continuar até que o outro esteja completamente concluído.

Até mesmo as chamadas assíncronas são síncronas até certo ponto. Seria impossível chamar o fim antes que o início seja concluído.

Joel Etherton
fonte