.NET 如何有效侦听引发的事件?
How does .NET efficiently listen for raised events?
它是否使用某种轮询事件队列的事件线程?此外,该技术是否因事件类型而异?有些事件是由程序本身引发的,例如单击按钮,而其他事件是由外部引发的,例如 FileSystemWatcher 读取的 FileCreated 事件。这些事件在幕后的处理方式不同吗?
TL;DR: Event listeners do not actually have to actively listen; they get called back by the event-triggering party. See the Observer pattern.
就像属性一样,实际上只不过是一组方法和添加的一些语法糖,.NET 事件也不过是其他东西(多播委托)和一些语法糖。
就像属性一样,您可以通过语言自动实现它们(例如 string Name { get; set; }
),事件通常也以非常相似的方式 "auto-implemented"(除非您专门实现一个事件你自己)。如果你想自己实现一个事件(这是非常罕见的事情),它可能看起来像这样(简化):
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
访问器中发生的那样)或通过 -=
运算符将其从调用列表中删除(如上面的 remove
访问器)。所以请注意 +=
和 -=
做不同的事情取决于它们是应用于事件(调用 add
或 remove
)还是委托(add/remove 通过对 Delegate.Combine
和 Delegate.Remove
).
的后台调用从内部调用列表中获取方法
事件订阅者不必轮询;当事件被触发时,他们将被调用。无论哪一方 raises/triggers 事件实际上只是调用 "backing-field" 委托;调用该委托意味着调用委托调用列表中的每个方法——即订阅者的事件处理程序方法。
这是一个非常广泛的话题,我只能合理地涵盖基础知识。这些机制并不特定于 .NET,它们适用于在 Windows 上运行的任何程序。操作系统或其他程序可以通过两种基本方式触发事件。
第一个是您假设的,按钮的 Click 事件以及几乎所有与 GUI 程序关联的事件的底层机制。核心 .NET 调用是 Application.Run(),它启动一个调度程序循环。也称为 "pumping the message loop"。一般的解决方案为producer-consumer problem。生成事件的基本 winapi 函数是 SendMessage() 和 PostMessage()。 .NET 程序具有将这些消息转换为您可以订阅的事件的管道,NativeWindow class 就是一个很好的例子。它的 WndProc() 方法在收到消息时运行。然后它可以根据特定消息引发特定事件。
第二个是操作系统可以在任意工作线程上对函数进行回调,通常是从线程池中提取的线程。 FileSystemWatcher 就是其中的一个示例,设置它的底层 winapi 函数是 ReadDirectoryChangesW()。它支持重叠 I/O,允许它异步操作。换句话说,您可以要求它立即开始工作并return。操作系统然后在作业完成时发出事件信号或进行回调。与第一种机制不同,这类事件的工作方式隐含在任意线程上触发。
了解更多有关 winapi 的信息是理解所有这些的必要条件。
活动使用 代表。让我们先看看委托,然后看看它们是如何被事件使用的。
代表
A delegate 有点像托管函数指针。您可以创建一个委托,在调用时,
调用它指向的函数。委托经过类型检查,因此它们的参数类型和 return 类型必须与您要调用的函数匹配。您通过 委托类型 指定参数和 return 类型。例如,我在这里为 return 一个 string
并且不带参数的函数定义了一个委托类型:
delegate string MyDelegate();
现在我可以实例化 MyDelegate
以使其指向我想要的任何函数。例如,我创建了一个指向 Do
静态函数的新委托 d
,并调用它。您可以自己尝试一下:
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<...>
and 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 -cast delegate.
this.Click += HandleButtonClick;
多播委托就像常规委托一样,但它可以调用多个函数,一个接一个,顺序不限。
当您使用事件时,您实际上是在调用委托来调用所有这些函数:
this.Click();
现在你知道事件是如何运作的了:委托。
它是否使用某种轮询事件队列的事件线程?此外,该技术是否因事件类型而异?有些事件是由程序本身引发的,例如单击按钮,而其他事件是由外部引发的,例如 FileSystemWatcher 读取的 FileCreated 事件。这些事件在幕后的处理方式不同吗?
TL;DR: Event listeners do not actually have to actively listen; they get called back by the event-triggering party. See the Observer pattern.
就像属性一样,实际上只不过是一组方法和添加的一些语法糖,.NET 事件也不过是其他东西(多播委托)和一些语法糖。
就像属性一样,您可以通过语言自动实现它们(例如 string Name { get; set; }
),事件通常也以非常相似的方式 "auto-implemented"(除非您专门实现一个事件你自己)。如果你想自己实现一个事件(这是非常罕见的事情),它可能看起来像这样(简化):
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
访问器中发生的那样)或通过 -=
运算符将其从调用列表中删除(如上面的 remove
访问器)。所以请注意 +=
和 -=
做不同的事情取决于它们是应用于事件(调用 add
或 remove
)还是委托(add/remove 通过对 Delegate.Combine
和 Delegate.Remove
).
事件订阅者不必轮询;当事件被触发时,他们将被调用。无论哪一方 raises/triggers 事件实际上只是调用 "backing-field" 委托;调用该委托意味着调用委托调用列表中的每个方法——即订阅者的事件处理程序方法。
这是一个非常广泛的话题,我只能合理地涵盖基础知识。这些机制并不特定于 .NET,它们适用于在 Windows 上运行的任何程序。操作系统或其他程序可以通过两种基本方式触发事件。
第一个是您假设的,按钮的 Click 事件以及几乎所有与 GUI 程序关联的事件的底层机制。核心 .NET 调用是 Application.Run(),它启动一个调度程序循环。也称为 "pumping the message loop"。一般的解决方案为producer-consumer problem。生成事件的基本 winapi 函数是 SendMessage() 和 PostMessage()。 .NET 程序具有将这些消息转换为您可以订阅的事件的管道,NativeWindow class 就是一个很好的例子。它的 WndProc() 方法在收到消息时运行。然后它可以根据特定消息引发特定事件。
第二个是操作系统可以在任意工作线程上对函数进行回调,通常是从线程池中提取的线程。 FileSystemWatcher 就是其中的一个示例,设置它的底层 winapi 函数是 ReadDirectoryChangesW()。它支持重叠 I/O,允许它异步操作。换句话说,您可以要求它立即开始工作并return。操作系统然后在作业完成时发出事件信号或进行回调。与第一种机制不同,这类事件的工作方式隐含在任意线程上触发。
了解更多有关 winapi 的信息是理解所有这些的必要条件。
活动使用 代表。让我们先看看委托,然后看看它们是如何被事件使用的。
代表
A delegate 有点像托管函数指针。您可以创建一个委托,在调用时,
调用它指向的函数。委托经过类型检查,因此它们的参数类型和 return 类型必须与您要调用的函数匹配。您通过 委托类型 指定参数和 return 类型。例如,我在这里为 return 一个 string
并且不带参数的函数定义了一个委托类型:
delegate string MyDelegate();
现在我可以实例化 MyDelegate
以使其指向我想要的任何函数。例如,我创建了一个指向 Do
静态函数的新委托 d
,并调用它。您可以自己尝试一下:
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<...>
and 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 -cast delegate.
this.Click += HandleButtonClick;
多播委托就像常规委托一样,但它可以调用多个函数,一个接一个,顺序不限。
当您使用事件时,您实际上是在调用委托来调用所有这些函数:
this.Click();
现在你知道事件是如何运作的了:委托。