Чтобы ответить на ваши вопросы:
- Создание события блокирует поток, если все обработчики событий реализованы синхронно.
- Обработчики событий выполняются последовательно, один за другим, в том порядке, в котором они подписаны на событие.
Меня тоже интересовал внутренний механизм 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
in Do()
, он просто вызывает последний конкатенированный делегат после загрузки аргумента:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
Как именно подписчик подписывается на событие?
И наконец Main
, что неудивительно, подписка на OnCall
событие осуществляется путем вызова add_OnCall
метода Foo
экземпляра.
Это общий ответ, отражающий поведение по умолчанию:
При этом каждый класс, предоставляющий события, может выбрать асинхронную реализацию своего события. IDesign предоставляет класс,
EventsHelper
который упрощает это.[Примечание] эта ссылка требует, чтобы вы указали адрес электронной почты для загрузки класса EventsHelper. (Я никоим образом не аффилирован)
источник
Делегаты, подписанные на событие, вызываются синхронно в том порядке, в котором они были добавлены. Если один из делегатов выдает исключение, следующие не будут вызываться.
Поскольку события определяются с помощью многоадресных делегатов, вы можете написать свой собственный механизм запуска, используя
и асинхронный вызов делегатов;
источник
События - это просто массивы делегатов. Пока вызов делегата является синхронным, события также синхронны.
источник
В общем, события синхронные. Однако есть некоторые исключения, такие как
System.Timers.Timer.Elapsed
событие, возникающее вThreadPool
потоке, еслиSyncronisingObject
имеет значение null.Документы: http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx
источник
События в C # выполняются синхронно (в обоих случаях), если вы не запускаете второй поток вручную.
источник
События синхронны. Вот почему жизненный цикл события работает именно так. Иниции происходят до загрузки, загрузки происходят до рендеринга и т. Д.
Если для события не указан обработчик, цикл просто продолжается. Если указано более одного обработчика, они будут вызываться по порядку, и один не может продолжить, пока другой не будет полностью завершен.
Даже асинхронные вызовы в определенной степени синхронны. Было бы невозможно назвать конец, пока не завершится начало.
источник