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.