这个问题分为两部分:
引发事件会阻塞该线程,还是异步开始执行EventHandlers并且线程会继续同时继续?
个人EventHandlers (订阅事件)是一个接一个地同步运行,还是异步运行而不保证其他人不同时运行?
答案 0 :(得分:69)
这是一般性答案,反映了默认行为:
话虽如此,提供事件的每个类都可以选择异步实现其事件。 IDesign提供了一个名为EventsHelper
的类,可以简化此操作。
[注意] 此链接要求您提供电子邮件地址以下载EventsHelper类。 (我不以任何方式加入)
答案 1 :(得分:22)
回答你的问题:
我也对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()
,它只需在加载arg后调用最终的连接委托:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
最后,在Main
中,并非令人惊讶的是,订阅OnCall
事件是通过调用add_OnCall
实例上的Foo
方法来完成的。
答案 2 :(得分:14)
按照添加的顺序同步调用订阅该事件的代理。如果其中一个代表抛出异常,那么后面的那些将不会被调用。
由于事件是使用多播委托定义的,因此您可以使用
编写自己的触发机制Delegate.GetInvocationList();
并异步调用委托;
答案 3 :(得分:12)
事件只是代表的数组。只要委托调用是同步的,事件也是同步的。
答案 4 :(得分:7)
通常,事件是同步的。但是有一些例外情况,例如System.Timers.Timer.Elapsed
如果ThreadPool
为空则在SyncronisingObject
线程上引发{{1}}事件。
文档:http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx
答案 5 :(得分:3)
C#中的事件同步运行(在两种情况下),只要您不手动启动第二个线程。
答案 6 :(得分:3)
事件是同步的。这就是事件生命周期以它的方式工作的原因。 Inits在加载之前发生,加载发生在渲染之前等等。
如果没有为事件指定处理程序,那么循环就会消失。如果指定了多个处理程序,它们将按顺序调用,并且一个处理程序无法继续,直到另一个处理程序完全完成。
即使异步调用在某种程度上也是同步的。在开始结束之前调用结束是不可能的。