上下文委托
这是一个抽象,可以(但不限于)促进构建多线程 WinForms UI。

引言
什么是 ContextDelegate?
ContextDelegate
是一种抽象,它简化了在任何事件驱动、单线程上下文中传递回调。上下文委托可以被描述为一种具有内置亲和性的委托,它亲和于创建它的“上下文”。当调用此类委托时,目标方法会在创建委托的原始上下文中异步调用。当回调接收者上下文是 UI 上下文时(例如 WinForms、WebForms),回调会在事件边界内的 UI 线程上分派。当回调接收者上下文没有特殊要求时,回调会在任何可用线程上分派(可能是调用线程)。
为什么需要 ContextDelegate?
从根本上说,客户端应用程序是一个事件处理引擎。应用程序代码通常由视觉对象和非视觉对象组成,它们集体在一个事件驱动的单线程上下文中执行。在更复杂的客户端应用程序中,视觉对象和非视觉对象的角色会通过 Model-View-Controller 设计模式来形式化。
在任何时刻,客户端应用程序只处理一个事件。大多数框架会将系统级别的异步活动(例如按键、鼠标单击或移动事件)作为简单的事件传递,并由客户端应用程序串行处理。没有用户界面对象需要允许多个系统事件在多个线程上相互竞争的可能性。
随着多线程客户端应用程序的出现,应用程序需要处理非系统异步事件(例如,从服务器到达数据、长时间计算的完成)。过去,这取决于应用程序开发人员来安排这些事件被安全地由与系统事件相同的上下文(即线程)处理。大多数由此产生的临时解决方案都涉及到编写复杂且易出错的混合线程安全和单线程逻辑。反过来,这最终破坏了事件驱动、单线程 UI 模型原有的简洁性和可预测性。
ContextDelegate
提供了一种透明的方式来传递任何应用程序生成的异步事件,其合同与系统级事件相同。这使得这些事件能够以与系统级事件(例如键盘单击)相同的简洁性来处理。
有什么实际例子吗?
发布/订阅客户端应用程序的一个典型模式涉及一组一个或多个线程,它们订阅发布者发布的更新,并调用回调来处理这些更新。回调必须在客户端(即 UI)线程上执行。回调必须以一种能够保持用户界面响应性的方式传递,即使更新频率很高。ContextDelegate
处理了使这一切成为可能的必要“管道”,从而最大限度地减少了应用程序开发人员的工作量。
ContextDelegate 的用法
虽然该机制涉及多个类型,但客户端只使用 ContextDelegate
类。多线程服务不知道它正在处理一个上下文敏感的回调。典型的用法模式涉及以下事件。

- 客户端代码创建一个“常规”的 .NET 委托。
- 客户端代码调用
static ContextDelegate.Create()
方法,从作为唯一参数传递的常规 .NET 委托创建上下文敏感委托。Create()
方法返回一个Delegate
类型。返回的委托引用原始委托,并隐含着对目标对象的强引用。 - 客户端代码将
Create()
方法返回的“上下文敏感”委托“传递”给多线程服务。 - 多线程服务调用“上下文敏感”委托。
- 上下文敏感委托在回调创建者指定的上下文中调用原始委托。
如果委托的目标方法带有 [ThreadNeutral]
属性,ContextDelegate.Create()
方法将不会执行任何特殊封送。这使得 ContextDelegate
机制能够透明地支持上下文敏感和上下文不敏感的观察者。
ContextDelegate 的实现
ContextDelegate
机制涉及类、接口、结构和属性。
名称 | 类型 | 目的 |
AtomicCount |
结构体 | 由回调调度器使用的多线程安全计数器,用于维护特定上下文的挂起回调数量的统计信息。 |
背景 |
类 | 回调的上下文由一个调度器、一个挂起回调队列和一个委托外观映射组成。 |
ContextDelegate |
类 | 由客户端代码使用的公共类,用于创建上下文敏感委托。 |
DelegateFactory |
类 | 默认工厂,用于重新创建没有外观的 .NET 委托。 |
外观 |
类 | 为支持上下文敏感分派的 .NET 委托提供一个外观。 |
Factory |
类 | 用于重新创建上下文委托。高级功能,普通应用程序代码不使用。 |
IDelegateFactory |
接口 | 用于从底层方法和目标对象重新创建上下文委托的接口。高级功能,普通应用程序代码不使用。 |
IDelegateProxy |
接口 | 用于访问底层方法或目标对象的接口。高级功能,普通应用程序代码不使用。 |
IScheduler |
接口 | 由上下文敏感委托调度器实现的接口。 |
MessageWnd |
类 | 一个非显示的窗口,用于由 STAScheduler 调度回调。 |
STAScheduler |
类 | 标准单元模型线程(即 WinForms 客户端代码)的调度器。该调度器确保回调的传递方式能够保证用户界面的响应性。 |
WebScheduler |
类 | ASP.NET 客户端应用程序的调度器。 |
ThreadNeutral |
attribute | 带有此属性的方法将会在任何可用线程(包括调用线程)上调用上下文敏感委托。 |
User32 |
类 | 单例类,暴露用于消息分派的选定的 Win32 API 调用。 |
WeakMap |
类 | 一个只维护键值弱引用的映射。WeakMap 由 Context 类用于保存外观。 |
ContextDelegate
机制目前支持具有以下参数签名的 .NET 委托:
(object sender, EventArgs args)
(IAsyncResult result)
(object returnValue)
()
Facade
类可以扩展以支持其他签名。该机制目前支持 WinForms 和 ASP.NET 应用程序的调度器。
ContextDelegate
机制中的类以以下方式交互:

ContextDelegate
事件 | |
1 | 创建一个带有关联调度器和分派队列的 Context 。 |
2 | 调用 Context 的 CreateDelegate 方法,为请求的委托签名创建一个 Facade 。 |
背景
事件 | |
3 | 为请求的委托签名创建一个 Facade 。 |
5 | 为关联调度器排队挂起的回调。调用关联调度器的 Schedule 方法来调度回调的分派。 |
外观
事件 | |
4 | 调用 Context 的 Post 方法来调度回调。 |
STAScheduler
事件 | |
6 | 调用 MessageWnd 的 Post 方法来发布一个 WM_USER 消息。 |
7 | 提供消息处理过程来处理发布到 MessageWnd 的消息。此消息处理过程将在创建调度器的客户端的上下文中(即线程)被调用。 |
8 | 调用 Context 的 Dispatch 方法来实际调用回调。 |
附件
在 ZIP 文件附件中提供了 ContextDelegate
实现和示例 WinForms 客户端应用程序的 Visual Studio 2005 项目。使用 CtxDelegateSample.sln 来构建和执行 ContextDelegate
和示例。
ContextDelegate
实现以高更新速率运行在生产环境的多线程服务中。尽管如此,实现中仍有一些方面可以得到改进或进一步泛化。这些在源代码中已适当地标记。
示例应用程序在一个列表框中显示来自后台订阅者线程的更新。后台线程不知道它正在调用一个需要 UI 上下文的方法。后台线程使用的委托与更新速率和更新计数一起被传递。来自线程过程的回调由 ContextDelegate
封送到 UI 线程。更新在 UI 线程中显示,即使在更新速率很高的情况下,用户界面也保持完全响应。
未来文章
在未来的文章中,将探讨 ContextDelegate
在基础设施服务中的更高级用法。将提供一个 ASP.NET 示例应用程序。将描述在高更新速率环境(例如,实时市场数据)中,在 MVC 模式下的解决方案。