它是否使用某种轮询事件队列的事件线程?此外,根据事件的类型,技术会有所不同吗?某些事件由程序本身引发,如按钮单击,而其他事件则在外部引发,如FileSystemWatcher读取的FileCreated事件。引擎盖下这些事件的处理方式是否不同?
答案 0 :(得分:11)
这是一个非常广泛的主题,我只能合理地涵盖基础知识。这些机制并非特定于.NET,它们适用于在Windows上运行的任何程序。操作系统或其他程序可以通过两种基本方式触发事件。
第一个是你假设的,按钮的Click事件的基础机制以及几乎所有与GUI程序相关的事件。核心.NET调用是Application.Run(),它启动一个调度程序循环。也称为“抽取消息循环”。 producer-consumer problem的一般解决方案。生成事件的基本winapi函数是SendMessage()和PostMessage()。 .NET程序具有管道,可以在您可以订阅的事件中转换这些消息,NativeWindow类就是一个很好的例子。它的WndProc()方法在收到消息时运行。然后,它可以根据特定消息提出特定事件。
第二个是操作系统可以对任意工作线程上的函数进行回调的地方,通常是从线程池中提取的一个函数。 FileSystemWatcher就是那个例子,设置它的底层winapi函数是ReadDirectoryChangesW()。它支持重叠I / O,允许它异步操作。换句话说,您可以要求它开始工作并立即返回。然后,操作系统发出事件信号或在作业完成时进行回调。这种事件的工作方式隐含的是,它们在任意线程上被触发,与第一种机制不同。
要了解所有这些,有必要了解有关winapi的更多信息。
答案 1 :(得分:6)
TL; DR:事件监听器实际上不必主动收听;他们被事件触发方召回。请参阅Observer pattern。
就像属性一样,实际上只不过是一组方法和添加了一些语法糖,.NET事件也不仅仅是其他东西(多播委托)和一些语法糖。
就像属性一样,您可以通过语言自动实现(例如string Name { get; set; }
)事件通常也以非常类似的方式“自动实现”(除非您自己专门实现事件) 。如果你想自己实现一个事件(这是非常罕见的事情),它可能看起来像这样(简化):
public event Action Completed
{
add // gets called for each `obj.Completed += value;`
{
if (completed == null)
{
completed = new Action(value);
}
else
{
completed += value;
}
}
remove // gets called for each `obj.Completed -= value;`
{
if (completed != null)
{
completed -= value;
}
}
}
private Action completed; // backing field (a delegate) for the event
与大多数数据属性一样,每个事件通常也有一个支持字段 - 即多播委托。订阅活动(Completed += …
)或取消订阅(-=
)会转化为对add
和remove
访问者方法的调用。
(多播)代表有一个内部方法invocation list。您可以通过+=
运算符添加方法(如上面add
访问器中所示),或通过-=
运算符从调用列表中删除它(如上所述{{1访问者)。请注意remove
和+=
执行不同的操作,具体取决于它们是应用于事件(调用-=
还是add
)还是应用于委托(添加/删除方法)从内部调用列表通过引擎盖调用到remove
和Delegate.Combine
)。
活动订阅者无需轮询;触发事件时会调用它们。无论什么方提出/触发事件实际上只是调用“后备字段”代表;并且调用该委托意味着调用委托的调用列表上的每个方法 - 即订阅者的事件处理程序方法。
答案 2 :(得分:3)
事件使用代理。让我们首先看看代表,然后是事件如何使用它们。
委托有点像托管函数指针。您可以创建一个委托,在调用时,
调用它指向的函数。代理是类型检查的,因此它们的参数类型和返回类型必须与要调用的函数匹配。您可以通过委托类型指定参数和返回类型。例如,在这里我为函数定义一个委托类型,该函数返回string
并且不带参数:
delegate string MyDelegate();
现在我可以实例化MyDelegate
,使其指向我想要的任何功能。例如,我创建了一个新的委托d
,它指向Do
静态函数,并调用它。你可以自己尝试一下:
class Program {
delegate string MyDelegate();
static string Do() {
return "DO!";
}
static void Main() {
MyDelegate d = new MyDelegate(Do);
Console.WriteLine(d()); // Prints: DO!
}
}
.NET框架有一些您可能熟悉的内置委托类型,例如Action<...>
和Func<TResult, ...>
代表系列。
好的,现在我们对代表有了基本的了解。让我们看一下它们在事件中的使用方式。
您通常会定义一个这样的新事件:
event EventHandler Click;
此处,EventHandler
是带有此签名的预定义委托类型:
public delegate void EventHandler(Object sender, EventArgs e)
请注意,委托与您为处理事件而编写的事件处理程序完全对应:
void HandleButtonClick(Object sender, EventArgs e) {
// Do something!
}
当您使用HandleButtonClick
运算符向Click
事件注册事件处理程序+=
时,它会将指向您的函数的委托添加到事件的 multi - 代表。
this.Click += HandleButtonClick;
多播委托就像一个普通的委托,但它可以一个接一个地调用多个函数,而不是特定的顺序。
当您使用该活动时,您实际上正在调用该委托来调用所有这些功能:
this.Click();
现在你知道事件是如何运作的:代表。