события C # синхронны? - PullRequest
       41

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

93 голосов
/ 18 августа 2011

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

  1. Блокирует ли событие событие поток или он запускает выполнение EventHandlers асинхронно, и поток продолжается одновременно?

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

Ответы [ 7 ]

69 голосов
/ 18 августа 2011

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

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

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

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

24 голосов
/ 01 декабря 2017

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

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

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

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

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

14 голосов
/ 18 августа 2011

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

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

Delegate.GetInvocationList();

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

12 голосов
/ 18 августа 2011

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

7 голосов
/ 18 августа 2011

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

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

3 голосов
/ 18 августа 2011

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

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

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

3 голосов
/ 18 августа 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...