Являются ли события С# синхронными?

В этом вопросе есть две части:

  • Возникает ли событие, блокирующее поток, или он запускает выполнение асинхронных событий EventHandlers, и поток продолжается в одно и то же время?

  • Являются ли отдельные EventHandlers (подписываются на событие) синхронно запускаются один за другим или выполняются они асинхронно без гарантии того, что другие не работают одновременно?

Ответ 1

Чтобы ответить на ваши вопросы:

  1. Возбуждение события блокирует поток, если все обработчики событий реализованы синхронно.
  2. Обработчики событий выполняются последовательно, один за другим, в том порядке, в котором они подписаны на событие.

Мне тоже было интересно узнать о внутреннем механизме 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:

enter image description here

Обратите внимание, что есть поле 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(), он просто вызывает окончательный сцепленный делегат после загрузки аргумента:

IL_0026:  callvirt   instance !1 class [mscorlib]System.Func'2<int32,string>::Invoke(!0)

Как именно подписчик подписывается на событие?

И наконец, в Main, что неудивительно, подписка на событие OnCall осуществляется путем вызова метода add_OnCall в экземпляре Foo.

Ответ 2

Это общий ответ, который отражает поведение по умолчанию:

  1. Да, он блокирует поток, если методы, подписывающиеся на событие, не являются асинхронными.
  2. Они исполняются один за другим. Это имеет другой поворот: если один обработчик события выдает исключение, обработчики событий, еще не выполненные, не будут выполняться.

Сказав это, каждый класс, предоставляющий события, может выбрать реализацию своего события асинхронно. IDesign предоставляет класс EventsHelper, который упрощает это.

[Примечание] по этой ссылке необходимо указать адрес электронной почты для загрузки класса EventsHelper. (Я никак не связан)

Ответ 3

Делегаты, подписавшиеся на это событие, синхронно вызываются в том порядке, в котором они были добавлены. Если один из делегатов выдает исключение, те, которые следуют за , не будут.

Поскольку события определены с помощью многоадресных делегатов, вы можете написать свой собственный механизм стрельбы, используя

Delegate.GetInvocationList();

и асинхронно вызывать делегатов;

Ответ 4

События - это просто массивы делегатов. Пока вызов делегата является синхронным, события также синхронны.

Ответ 5

В общем случае события синхронны. Однако есть некоторые исключения, такие как System.Timers.Timer.Elapsed событие, возникающее в потоке ThreadPool, если SyncronisingObject равно null.

Документы: http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx

Ответ 6

События в С# запускаются синхронно (в обоих случаях), если вы не запускаете второй поток вручную.

Ответ 7

События синхронны. Вот почему жизненный цикл события работает так, как он делает. Ины происходят до нагрузок, нагрузки происходят до рендеринга и т.д.

Если обработчик не указан для события, цикл просто пробивается. Если указано более одного обработчика, они будут вызваны по порядку, и один не может продолжаться до тех пор, пока другой не будет полностью завершен.

Даже асинхронные вызовы синхронны со степенью. Было бы невозможно назвать конец до того, как начало будет завершено.