C#事件同步吗?

时间:2011-08-18 11:11:21

标签: c# .net events delegates

这个问题分为两部分:

  1. 引发事件会阻塞该线程,还是异步开始执行EventHandlers并且线程会继续同时继续?

  2. 个人EventHandlers (订阅事件)是一个接一个地同步运行,还是异步运行而不保证其他人不同时运行?

7 个答案:

答案 0 :(得分:69)

这是一般性答案,反映了默认行为:

  1. 是的,如果订阅该事件的方法不是异步的,它会阻塞该线程。
  2. 他们一个接一个地执行。这有另一个转折点:如果一个事件处理程序抛出异常,那么尚未执行的事件处理程序将不会被执行。
  3. 话虽如此,提供事件的每个类都可以选择异步实现其事件。 IDesign提供了一个名为EventsHelper的类,可以简化此操作。

    [注意] 此链接要求您提供电子邮件地址以下载EventsHelper类。 (我不以任何方式加入)

答案 1 :(得分:22)

回答你的问题:

  1. 如果事件处理程序全部同步实现,则引发事件会阻塞线程。
  2. 事件处理程序按照订阅事件的顺序依次执行。
  3. 我也对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()

    如何实施订阅和取消订阅?

    这是CIL中缩写的add_OnCall实现。有趣的是它使用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方法来完成的。

答案 2 :(得分:14)

按照添加的顺序同步调用订阅该事件的代理。如果其中一个代表抛出异常,那么后面的那些将不会被调用。

由于事件是使用多播委托定义的,因此您可以使用

编写自己的触发机制
Delegate.GetInvocationList();

并异步调用委托;

答案 3 :(得分:12)

事件只是代表的数组。只要委托调用是同步的,事件也是同步的。

答案 4 :(得分:7)

通常,事件是同步的。但是有一些例外情况,例如System.Timers.Timer.Elapsed如果ThreadPool为空则在SyncronisingObject线程上引发{{1}}事件。

文档:http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx

答案 5 :(得分:3)

C#中的事件同步运行(在两种情况下),只要您不手动启动第二个线程。

答案 6 :(得分:3)

事件是同步的。这就是事件生命周期以它的方式工作的原因。 Inits在加载之前发生,加载发生在渲染之前等等。

如果没有为事件指定处理程序,那么循环就会消失。如果指定了多个处理程序,它们将按顺序调用,并且一个处理程序无法继续,直到另一个处理程序完全完成。

即使异步调用在某种程度上也是同步的。在开始结束之前调用结束是不可能的。