Синхронны ли события C #?

104

Этот вопрос состоит из двух частей:

  1. Имеет ли повышение события блокировать поток, или он начинается выполнение EventHandlers асинхронно и идет поток продолжается в то же время?

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

Александр Берд
источник

Ответы:

37

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

  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:

введите описание изображения здесь

Обратите внимание, что есть поле 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.

Как вызывается событие?

Чтобы вызвать OnCallin Do(), он просто вызывает последний конкатенированный делегат после загрузки аргумента:

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

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

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

KFL
источник
3
Отлично сработано!! Я так давно не задавал этот вопрос. Если вы можете поместить вверху словоблудие, которое напрямую отвечает на мой вопрос, состоящий из двух частей (например, «ответ №1 - нет; ответ №2 - нет»), то я сделаю это официальным ответом. Я готов поспорить, что ваш пост отвечает на мои первоначальные вопросы, но, поскольку я больше не использую C # (а другие гуглеры могут быть новичками в этих концепциях), я прошу многословия, которое делает ответы очевидными.
Александр Берд
Спасибо @AlexanderBird, только что отредактировал, чтобы ответы были вверху.
КЛС
@KFL, все еще неясно, я как раз собирался оставить такой же комментарий, что и Алекс. Было бы полезно простое «Да, они синхронны»
johnny 5
71

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

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

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

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

Дэниел Хилгарт
источник
Я прочитал несколько сообщений на форуме, из которых два противоречили первому пункту, но без надлежащей причины. Я не сомневаюсь в вашем ответе (он совпадает с тем, что я испытал до сих пор), есть ли официальная документация по первому пункту? Я должен быть уверен в этом, но мне трудно найти что-либо официальное по этому поводу.
Adam LS
@ AdamL.S. Все дело в том, как называется мероприятие. Так что это действительно зависит от класса, обеспечивающего событие.
Daniel Hilgarth
14

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

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

Delegate.GetInvocationList();

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

Владимир
источник
12

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

Андрей Агибалов
источник
3

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

Док Браун
источник
Как насчет того, чтобы я использую обработчик событий async? Будет ли он работать в другом потоке? Я слышал об "Async полностью", но похоже, что у обработчиков событий async есть свой поток? Я не понимаю: / Не могли бы вы просветить меня?
Вингер Сендон
3

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

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

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

Джоэл Этертон
источник