Para responder às suas perguntas:
- Gerar um evento bloqueia o encadeamento se os manipuladores de eventos forem todos implementados de forma síncrona.
- 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 event
e suas operações relacionadas. Então, escrevi um programa simples e costumava ildasm
vasculhar 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:
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_OnCall
implementação abreviada em CIL. A parte interessante é que ele usa Delegate.Combine
para 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 OnCall
em 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, Main
não é de surpreender que a inscrição no OnCall
evento seja feita chamando o add_OnCall
método na Foo
instância.
Esta é uma resposta geral e reflete o comportamento padrão:
Dito isso, cada classe que fornece eventos pode escolher implementar seu evento de forma assíncrona. IDesign fornece uma classe chamada
EventsHelper
que 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)
fonte
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
e invocar os delegados de forma assíncrona;
fonte
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.
fonte
Em geral, os eventos são síncronos. No entanto, existem algumas exceções, como
System.Timers.Timer.Elapsed
evento sendo gerado em umThreadPool
thread seSyncronisingObject
for nulo.Documentos: http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx
fonte
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.
fonte
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.
fonte