这个问题分为两个部分:

  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相关问答推荐

如何处理以用户为中心的CSP现时值?(uc-lock.bundle.js)

当 Func 委托需要接口作为参数时,它是如何工作的?

.NET 中是否有一种简单的方法来获得数字的st、nd、rd和th结尾?

具有透明背景且包含不透明控件的 WPF 窗口

File.ReadAllLines() 和 File.ReadAllText() 有什么区别?

是否有 TLS 1.2 的 .NET 实现?

从 List 到数组 T[] 的转换

在 C# 中将字符串转换为十六进制字符串

MemoryStream.Close() 或 MemoryStream.Dispose()

什么是 C# 中的自动属性,它们的用途是什么?

析构函数、dispose 和 finalize 方法的区别

Iif 在 C# 中等效

C# 中的 override 和 new 关键字有什么区别?

如何仅在需要时提升权限?

C# (.NET) 设计缺陷

C# 应用程序中的全局键盘捕获

如何在不使用 3rd 方库的情况下登录 C#?

判断任意字符串是否为有效文件名的最简单方法

Dictionary.Add 与 Dictionary[key]=value 的区别

在构建事件命令行中放置注释的正确方法?