这个问题分为两个部分:

  1. 事件是否会阻止线程,还是会异步启动EventHandler的执行,同时线程继续运行?

  2. individual EventHandlers(订阅了事件)是一个接一个地同步运行,还是在不能保证其他人不会同时运行的情况下异步运行?

推荐答案

是的,它们是同步的.

要回答您的问题:

  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

请注意,有一个field OnCall和一个event OnCall.字段OnCall显然是支持属性.这只是一个Func<int, string>,没什么特别的.

现在有趣的部分是:

  • add_OnCall(Func<int, string>)
  • remove_OnCall(Func<int, string>)
  • 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

同样,remove_OnCall中使用Delegate.Remove.

如何调用事件?

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

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

订阅者是如何订阅活动的?

最后,在Main中,不出所料,订阅OnCall事件是通过调用Foo实例上的add_OnCall方法来完成的.

.net相关问答推荐

SeriLog LogConext.PushProperty在ASP.NET MVC 5中不能使用OWIN中间件

NETSDK1083:无法识别指定的 RuntimeIdentifierwin10-x64

使用 SSH.NET 查找具有特定文件名的最新 SFTP 文件

单线程单元 - 无法实例化 ActiveX 控件

将 BitmapImage 转换为 Bitmap,反之亦然

在 WinForms 应用程序中查找焦点控件的首选方法是什么?

使用 IIS Express 托管网站(临时)

哪个单元测试框架?

C# 的 Actors 有什么好的实现吗?

C# 属性实际上是方法吗?

您可以在 C# 代码中捕获本机异常吗?

.NET 如何判断路径是否是文件而不是目录?

检索字典值最佳实践

如何使用配置转换删除 ConnectionString

是否有可用的 WPF 备忘单?

何时使用抽象类?

是否有 Linq 方法可以将单个项目添加到 IEnumerable

无法使用 Unity 将依赖项注入 ASP.NET Web API 控制器

当它被抛出和捕获时,不要在那个异常处停止调试器

NServiceBus 与 MassTransit