C#事件是否同步?


104

这个问题有两个部分:

  1. 是否提高事件阻塞线程,或者它异步启动事件处理器的执行和线程便继续在同一时间?

  2. 个别事件处理器同步运行接二连三(订阅事件),或者是他们还不能保证别人不会在同一时间运行的异步运行?

Answers:


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_OnCallCIL中的缩写实现。有趣的部分是它用于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(),它仅在加载arg之后调用最终的串联委托:

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

订户到底如何订阅事件?

最后,在Main并不OnCall令人惊讶地,在中,通过add_OnCallFoo实例上调用方法来订阅事件。


3
做得好!!我问这个问题已经很久了。如果您可以在顶部直接回答我的两部分问题(即“#1答案为否;#2答案为否”),则将其作为正式答案。我押注您的帖子作为回答我的原始问题的全部内容,但是由于我不再使用C#(并且其他Google员工可能对这些概念还不熟悉),所以我要用语气使答案显而易见。
亚历山大·伯德

感谢@AlexanderBird,只需对其进行编辑即可将答案放在顶部。
KFL

@KFL,仍然不清楚,我将要留下与亚历克斯相同的评论。一个简单的“是的,它们是同步的”将很有帮助
johnny 15年

71

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

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

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

[注意]此链接要求您提供一个电子邮件地址以下载EventsHelper类。(我没有任何关系)


我读了一些论坛帖子,其中两个与第一点相矛盾,但没有提供适当的理由。我毫不怀疑您的回答,(它与我到目前为止的经验相符)关于第一点有任何官方文档吗?我需要确定这一点,但是我很难在这个确切的事情上找到任何官方的东西。
亚当LS

@ AdamL.S。这与事件的调用方式有关。因此,这实际上取决于提供事件的类。
Daniel Hilgarth '17

14

订阅事件的委托将按照添加的顺序进行同步调用。如果其中一位代表抛出异常,则不会调用以下代表。

由于事件是使用多播委托定义的,因此您可以使用以下代码编写自己的触发机制

Delegate.GetInvocationList();

异步调用委托;




3

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


那我使用异步事件处理程序呢?然后它将在另一个线程中运行吗?我听说过“一路异步”,但是异步事件处理程序似乎有自己的线程?我不明白://请您赐教一下吗?
Winger Sendon '16

3

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.