Чтобы ответить на ваши вопросы:
- Вызов события блокирует поток, если все обработчики событий реализованы синхронно.
- Обработчики событий выполняются последовательно, один за другим, в том порядке, в котором они подписаны на событие.
Мне тоже было интересно узнать о внутреннем механизме event
и связанных с ним операциях. Поэтому я написал простую программу и использовал ildasm
, чтобы разобраться в ее реализации.
Краткий ответ:
- нет никакой асинхронной операции, связанной с подпиской или вызовом событий.
- Событие реализовано с помощью вспомогательного поля делегата того же типа, что и делегат
- подписка осуществляется с
Delegate.Combine()
- отмена подписки производится с
Delegate.Remove()
- Вызов вызывается простым вызовом окончательного объединенного делегата
Вот что я сделал. Программа, которую я использовал:
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();
}
}
Вот реализация Foo:
Обратите внимание, что есть поле OnCall
и событие OnCall
. Поле OnCall
, очевидно, является вспомогательным свойством. И это всего лишь Func<int, string>
, ничего особенного здесь.
Теперь интересные части:
add_OnCall(Func<int, string>)
remove_OnCall(Func<int, string>)
- и как
OnCall
вызывается в Do()
Как осуществляется подписка и отмена подписки?
Вот сокращенная add_OnCall
реализация в CIL. Интересно, что он использует Delegate.Combine
для объединения двух делегатов.
.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
Аналогично, Delegate.Remove
используется в remove_OnCall
.
Как вызывается событие?
Чтобы вызвать OnCall
в Do()
, он просто вызывает окончательный объединенный делегат после загрузки arg:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
Как именно подписчик подписывается на событие?
И, наконец, в Main
, что неудивительно, подписка на событие OnCall
осуществляется путем вызова метода add_OnCall
в экземпляре Foo
.