Щоб відповісти на ваші запитання:
- Підвищення події блокує потік, якщо всі обробники подій реалізовані синхронно.
- Обробники подій виконуються послідовно, один за одним, у порядку, коли вони підписані на подію.
Мені теж було цікаво про внутрішній механізм 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
екземплярі.