【发布时间】:2011-10-29 16:55:23
【问题描述】:
这个问题有两个部分:
引发一个事件会阻塞线程,还是它异步开始执行 EventHandlers 并且线程同时继续?
各个 EventHandlers(订阅事件)是一个接一个地同步运行,还是异步运行,不保证其他人不会同时运行? p>
【问题讨论】:
这个问题有两个部分:
引发一个事件会阻塞线程,还是它异步开始执行 EventHandlers 并且线程同时继续?
各个 EventHandlers(订阅事件)是一个接一个地同步运行,还是异步运行,不保证其他人不会同时运行? p>
【问题讨论】:
这是一个通用答案,反映了默认行为:
话虽如此,每个提供事件的类都可以选择异步实现其事件。 IDesign 提供了一个名为 EventsHelper 的类来简化此操作。
[注意]此链接要求您提供电子邮件地址以下载 EventsHelper 类。 (我不以任何方式隶属)
【讨论】:
是的,它们是同步的。
回答您的问题:
我也很好奇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>)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 方法完成的。
【讨论】:
订阅事件的委托按照添加的顺序同步调用。如果其中一个代表抛出异常,则不会调用 后面的。
由于事件是使用多播委托定义的,因此您可以使用
编写自己的触发机制Delegate.GetInvocationList();
并异步调用委托;
【讨论】:
事件只是代表的数组。只要委托调用是同步的,事件也是同步的。
【讨论】:
通常,事件是同步的。但是也有一些例外,例如如果 SyncronisingObject 为 null,则在 ThreadPool 线程上引发 System.Timers.Timer.Elapsed 事件。
文档:http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx
【讨论】:
只要您不手动启动第二个线程,C# 中的事件就会同步运行(在这两种情况下)。
【讨论】:
事件是同步的。这就是事件生命周期以它的方式工作的原因。初始化发生在加载之前,加载发生在渲染之前等等。
如果没有为事件指定处理程序,则循环将立即结束。如果指定了多个处理程序,它们将按顺序调用,一个不能继续,直到另一个完全完成。
即使是异步调用在一定程度上也是同步的。在 begin 完成之前调用 end 是不可能的。
【讨论】: