C# 事件是同步的吗?

问题描述 投票:0回答:7

这个问题有两个部分:

  1. raise事件是否会阻塞线程,或者它是否开始异步执行EventHandler并且线程同时继续运行?

  2. 各个事件处理程序(订阅事件)是相继同步运行,还是异步运行且不能保证其他事件处理程序不会同时运行?

c# .net events delegates
7个回答
82
投票
这是一般答案,反映了默认行为:

    是的,如果订阅事件的方法不是异步的,它会阻塞线程。
  1. 他们一个接一个被处决。这还有另一个转折点:如果一个事件处理程序抛出异常,则尚未执行的事件处理程序将不会被执行。
话虽如此,每个提供事件的类都可以选择异步实现其事件。

IDesign 提供了一个名为 EventsHelper

 的类来简化此操作。

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


66
投票
是的,它们是同步的。

回答您的问题:

    如果事件处理程序全部同步实现,则引发事件确实会阻塞线程。
  1. 事件处理程序按照订阅事件的顺序依次执行。
我也很好奇

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()
订阅和取消订阅是如何实现的?

这是 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()
,它只需在加载参数后调用最终的串联委托:

IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
订阅者到底如何订阅事件?

最后,在

Main

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


15
投票
订阅事件的委托按照添加顺序同步调用。如果其中一个委托抛出异常,则

之后的委托将不会被调用。

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

编写自己的触发机制

Delegate.GetInvocationList();

并异步调用委托;


13
投票
事件只是委托数组。只要委托调用是同步的,事件也是同步的。


9
投票
一般来说,事件是同步的。但是也有一些例外,例如如果

System.Timers.Timer.Elapsed

 为 null,则在 
ThreadPool
 线程上引发 
SyncronisingObject
 事件。

文档:

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


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


4
投票
事件是同步的。这就是事件生命周期如此运作的原因。初始化发生在加载之前,加载发生在渲染之前等等。

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

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

© www.soinside.com 2019 - 2024. All rights reserved.