【问题标题】:Are C# events synchronous?C# 事件是同步的吗?
【发布时间】:2011-10-29 16:55:23
【问题描述】:

这个问题有两个部分:

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

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

【问题讨论】:

    标签: c# .net events delegates


    【解决方案1】:

    这是一个通用答案,反映了默认行为:

    1. 是的,如果订阅事件的方法不是异步的,它会阻塞线程。
    2. 它们一个接一个地被执行。这有另一个转折点:如果一个事件处理程序引发异常,则尚未执行的事件处理程序将不会被执行。

    话虽如此,每个提供事件的类都可以选择异步实现其事件。 IDesign 提供了一个名为 EventsHelper 的类来简化此操作。

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

    【讨论】:

    • 我看过一些论坛帖子,其中有两个与第一点相矛盾,但没有提供适当的理由。我不怀疑你的回答,(它与我迄今为止所经历的相符)是否有关于第一点的官方文件?我需要确定这一点,但我很难找到关于这件事的任何官方消息。
    • @AdamL.S.这是如何调用事件的问题。所以这真的取决于提供事件的类。
    【解决方案2】:

    是的,它们是同步的。

    回答您的问题:

    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&lt;int, string&gt;,这里没什么特别的。

    现在有趣的部分是:

    • add_OnCall(Func&lt;int, string&gt;)
    • remove_OnCall(Func&lt;int, string&gt;)
    • 以及如何在Do() 中调用OnCall

    订阅和退订是如何实现的?

    这是 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

    如何调用事件?

    要在Do() 中调用OnCall,它只需在加载arg 后调用最终连接的委托:

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

    订阅者究竟是如何订阅事件的?

    最后,在Main 中,毫不奇怪,订阅OnCall 事件是通过在Foo 实例上调用add_OnCall 方法完成的。

    【讨论】:

    • 干得好!!我已经很久没有问这个问题了。如果您可以将措辞放在顶部,直接回答我的两部分问题(即“#1 答案是否定的;#2 答案是否定的”),那么我会将其作为官方答案。我打赌你的帖子是回答我最初的问题的所有部分,但由于我不再使用 C#(其他谷歌人可能对这些概念不熟悉),这就是为什么我要求措辞使答案显而易见。
    • 感谢@AlexanderBird,刚刚编辑它以将答案放在顶部。
    • @KFL,目前还不清楚,我正要留下与亚历克斯相同的评论。一个简单的“是的,它们是同步的”会很有帮助
    【解决方案3】:

    订阅事件的委托按照添加的顺序同步调用。如果其中一个代表抛出异常,则不会调用 后面的。

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

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

    并异步调用委托;

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

          • 我使用异步事件处理程序怎么样?然后它会在另一个线程中运行吗?我听说过“一直异步”,但似乎异步事件处理程序有自己的线程?我不明白:/你能告诉我吗?
          【解决方案7】:

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

          如果没有为事件指定处理程序,则循环将立即结束。如果指定了多个处理程序,它们将按顺序调用,一个不能继续,直到另一个完全完成。

          即使是异步调用在一定程度上也是同步的。在 begin 完成之前调用 end 是不可能的。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-09-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-07-21
            • 2019-07-06
            • 1970-01-01
            相关资源
            最近更新 更多