EventBroker:用于同步和异步、松耦合事件处理的通知组件






4.89/5 (23投票s)
EventBroker 是一个用于(异步)松耦合事件处理的通知组件。
- 下载源代码 - 224 KB
有关如何运行代码的说明,请参阅下面的下载部分。
前言
本文是对 Daniel Grunwald 的文章 Weak Events in C# 的回应。
引言
EventBroker
是一个您可以在系统中用于触发和接收通知的组件。
特点
- 松耦合:
- 线程同步:
- 与发布者相同的线程(同步)
- 后台线程(异步)
- 用户界面线程(同步或异步)
- 多个发布者/订阅者:
- 弱引用:
- 范围:
- 每个
EventBroker
的范围 - 具有分层命名的范围
订阅者无需了解发布者。两者只需知道 EventTopic
URI(一个唯一标识系统中 EventTopic
的字符串)。这有助于构建松耦合的系统。
订阅者定义订阅处理程序在哪种线程上执行
发布者可以限制事件订阅为同步或异步。
多个发布者/订阅者可以触发/处理同一个 EventTopic
。
发布者和订阅者通过弱引用进行引用,这不会阻止它们被垃圾回收——就像在常规事件注册模型中一样。
只有在同一个 EventBroker
上注册的发布者和订阅者在触发事件时才会被连接起来。通常,您会在系统中只使用一个 EventBroker
来处理所有事件通知。然而,在特殊情况下,为子系统定义一个新的 EventBroker
是很有用的。这使您能够为事件通知定义一个范围。
发布者和订阅者可以进行分层命名,并且事件可以是全局的、仅对父级或仅对子级。发布者和订阅者都可以定义它们发布到/接收自的范围。
背景
此 EventBroker
基于 Microsoft 的 Composite (UI) Application Block 中的 EventBroker
。有关差异,请参阅下面的与 CAB EventBroker 的比较部分。
使用代码
示例发布者
发布事件主题
public class Publisher
{
[EventPublication("topic://EventBrokerSample/SimpleEvent")]
public event EventHandler SimpleEvent;
///<summary>Fires the SimpleEvent</summary>
public void CallSimpleEvent()
{
SimpleEvent(this, EventArgs.Empty);
}
}
将发布者注册到您的事件代理(您必须在代码中的某个地方保留事件代理的实例)。示例假设有一个服务为我们保留事件代理实例
EventBroker eb = Service.EventBroker;
Publisher p = new Publisher();
eb.Register(p);
在注册发布者时,事件代理会检查发布者是否发布了事件(带有 EventPublication
属性的事件)。
示例订阅者
订阅事件主题
public class Subscriber
{
[EventSubscription(
"topic://EventBrokerSample/SimpleEvent",
typeof(Handlers.Publisher))]
public void SimpleEvent(object sender, EventArgs e)
{
// do something useful or at least funny
}
}
将订阅者注册到事件代理
EventBroker eb = Service.EventBroker;
Subscriber s = new Subscriber();
eb.Register(s);
事件代理在注册时会检查订阅者是否订阅了事件主题(带有 EventSubscription
属性的方法)。
如果发布者触发了订阅者已注册的事件主题,则事件代理会通过调用订阅处理程序方法,使用发布者用于触发事件的 sender
和 Eventargs
,将它们中继给订阅者。
发布选项
简单
[EventPublication("Simple")]
public event EventHandler SimpleEvent;
使用自定义 Eventargs
注意:CustomEventArgs
只需要继承自 EventArgs
。
[EventPublication("CustomEventArgs")]
public event EventHandler<CustomEventArguments> CustomEventArgs;
一次发布多个事件主题
[EventPublication("Event1")]
[EventPublication("Event2")]
[EventPublication("Event3")]
public event EventHandler MultiplePublicationTopics;
仅允许同步订阅处理程序
有关更多详细信息,请参阅与 CAB EventBroker/订阅处理程序限制部分。
[EventPublication("test", HandlerRestriction.Synchronous)]
public event EventHandler AnEvent;
仅允许异步订阅处理程序
有关更多详细信息,请参阅与 CAB EventBroker/订阅处理程序限制部分。
[EventPublication("test", HandlerRestriction.Asynchronous)]
public event EventHandler AnEvent;
订阅选项
简单
[EventSubscription("Simple", typeof(Handlers.Publisher)]
public void SimpleEvent(object sender, EventArgs e) {}
自定义 Eventargs
[EventSubscription("CustomEventArgs"), typeof(Handlers.Publisher))]
public void CustomEventArgs(object sender, CustomEventArgs e) {}
订阅多个事件主题
[EventSubscription("Event1", typeof(Handlers.Publisher))]
[EventSubscription("Event2", typeof(Handlers.Publisher))]
[EventSubscription("Event3", typeof(Handlers.Publisher))]
public void MultipleSubscriptionTopics(object sender, EventArgs e) {}
在后台线程上执行处理程序(异步)
事件代理创建一个工作线程来在该线程上执行处理程序方法。发布者可以立即继续处理。
[EventSubscription("Background", typeof(Handlers.Background))]
public void BackgroundThread(object sender, EventArgs e) {}
在 UI 线程上执行处理程序
如果从后台工作线程调用用户界面组件以更新用户界面,请使用此选项——不再需要 Control.Invoke(...)
。
[EventSubscription("UI", typeof(Handlers.UserInterface))]
public void UI(object sender, EventArgs e) {}
请注意,如果您使用 UserInterface
处理程序,则必须确保您是在用户界面线程上注册订阅者。否则,EventBroker
将无法切换到用户界面线程,并会抛出异常。
在 UI 线程上异步执行处理程序
与上面相同,但发布者不会被阻塞,直到订阅者处理完事件。
[EventSubscription("UIAsync", typeof(Handlers.UserInterfaceAsync))]
public void UI(object sender, EventArgs e) {}
直接在 EventBroker 上触发事件主题
事件主题可以直接在 EventBroker
上触发,无需注册发布者。当您需要从一个只存在很短时间的对象的对象触发事件主题时,这一点非常有用。
这种情况的一个好例子是计划作业执行。在计划的时间,会实例化并执行作业类的实例。注册此实例、触发事件、然后再次取消注册会很麻烦——直接在 EventBroker
上触发事件要容易得多。
eventBroker.Fire("topic", sender, eventArgs);
范围
有时需要限制事件发布范围。这可以通过两种方式实现
多个事件代理实例
订阅者只能监听在同一事件代理上注册的发布者的事件。因此,事件代理会自动构建一个范围。
这是在应用程序中拥有多个事件处理范围的最简单解决方案,应始终优先考虑。然而,有时您需要在单个事件代理内的对象中更精确地控制范围。这种情况将在下一节中介绍。
分层命名
发布者和订阅者可以通过实现 INamedItem
接口来命名。该接口提供一个单一的属性
string EventBrokerItemName { get; }
这允许在事件代理中标识一个对象,而发布和订阅属性始终绑定到类。
命名是分层的,使用与命名空间相同的方案
Test
是Test.MyPublisher1
的父级Test.MyName1
是Test.MyName2
的同级Test.MyName1.Subname
是Test.MyName1
的子级Test.MyName1
是Test.MyName1
的同卵双胞胎(两个同名对象是同卵双胞胎)
现在,发布者可以通过在发布属性中定义范围来定义它要发布事件的范围
[EventPublication("Topic")]
public event EventHandler PublishedGlobally;
[EventPublication("Topic", typeof(ScopeMatchers.PublishToParents)]
public event EventHandler PublishedToParentsAndTwinsOnly;
[EventPublication("Topic", typeof(ScopeMatchers.PublishToChildren)]
public event EventHandler PublishedToChildrenAndTwinsOnly;
第一个事件是全局事件,所有订阅者都可以接收。第二个事件仅传递给发布者的父级或同卵双胞胎订阅者。第三个事件仅传递给发布者的子级或同卵双胞胎订阅者。
订阅者可以相应地定义它要接收事件的范围
[EventSubscription("Topic", typeof(Handlers.Publisher)]
public void GlobalHandler(object sender, EventArgs e)
[EventSubscription(
"Topic",
typeof(Handlers.Publisher),
typeof(ScopeMatchers.SubscribeToParents)]
public void ParentHandler(object sender, EventArgs e)
[EventSubscription(
"Topic",
typeof(Handlers.Publisher),
typeof(ScopeMatchers.SubscribeToChildren)]
public void ChildrenHandler(object sender, EventArgs e)
第一个订阅是全局订阅,将处理传递给它的所有事件。仅当订阅者的父级触发事件时,第二个订阅才会被调用。仅当订阅者的子级触发事件时,第三个订阅才会被调用。
同卵双胞胎(同名但不同的对象)会得到特殊处理。同卵双胞胎总是与其同卵双胞胎的父级和子级,因此将始终接收其同卵双胞胎的所有事件。
与 CAB EventBroker 的比较
如背景部分所述,我的 EventBroker
基于 Microsoft 的 Practices and Patterns 组的 CAB(Composite UI Application Block)中的 EventBroker
。
本节描述了差异。
Standalone
bbv.Common EventBroker
可独立使用。无论何时需要通知,您都可以在项目中使用它,没有任何框架限制,而 CAB 会强制要求。
开发人员指南
我尝试以一种尽可能早地暴露错误的方式实现 EventBroker
。我将举例说明这意味着什么
- 如果发布的事件提供的
EventHandler
类型与订阅处理程序提供的签名不匹配,则会在注册时抛出异常,而不是在事件触发时。 - 如果您使用
UserInterface
或UserInterfaceAsync
订阅处理程序,并且当前线程不是用户界面线程(即不存在WindowsFormsSynchronizationContext
),则会在注册时抛出异常。这会导致跨线程异常。
订阅处理程序限制
注意:此功能仅在托管在 Sourceforge.net 上的版本中可用(请参阅下载部分),而不在附加的解决方案中(它太新了 ;-))。
我们遇到了一个问题,某些发布者必须确保所有订阅者同步处理其事件。例如,如果您发布一个取消事件,为所有订阅者提供取消当前操作的方法。只有当没有订阅者将 CancelEventArgs
上的 Cancel
属性设置为 true
时,发布者才能继续其操作。因此,发布者必须将此事件的所有订阅限制为同步。
[EventPublication("test", HandlerRestriction.Synchronous)]
public event EventHandler AnEvent;
如果订阅者为此事件注册了异步处理程序,则会抛出异常。请注意,异常是在注册时抛出的,而不是在事件触发时。这大大简化了编写一致的代码。
此外,发布者可以限制订阅处理程序为异步,因为发布者不想被阻塞。
[EventPublication("test", HandlerRestriction.Asynchronous)]
public event EventHandler AnEvent;
日志记录
bbv.Common
的 EventBroker
在日志消息方面相当“健谈”。这使您能够看到发布者何时触发事件,它们如何路由到订阅者,如何处理它们,以及发生了哪些异常。
注意:bbv.Common
使用 log4net 来记录消息。这意味着您可以为每个组件配置要记录的消息级别。
可扩展性
ThreadOption --> Handler
我用可扩展的 Handlers 替换了 enum ThreadOption
,以定义事件的处理方式(同步、异步)。
Scope --> Scope Matcher
您可以实现自己的范围匹配器,以提供适合您需求的事件层次结构,而不是使用 enum 指定范围。
内部
下次更新本文时,我们将深入探讨其内部机制。在此之前,请参考下载中提供的源代码。
下载
本文顶部的下载包含三个 Visual Studio 2008 项目
- bbv.Common.EventBroker:事件代理组件。
- bbv.Common.EventBroker.Test:单元测试(NUnit)。
- bbv.Common.EventBroker.Sample:一个小型示例应用程序(事件代理的基本用法)。
bbv.Common.EventBroker 是一个更大的库的一部分,其中包含其他一些很棒的组件,可以在 这里 找到。
此下载只是一个精简版。要试用,请打开解决方案,将启动项目设置为bbv.Common.EventBroker.Sample,然后按 F5。
请注意,附加到本文的版本不是最新版本。请务必查看 Sourceforge 页面以获取最新版本。
许可证
请注意,bbv.Common.EventBroker 在 Apache License 2.0 下许可,但由于 EventBroker
包含 Microsoft CAB 的部分内容,因此必须应用额外的许可限制,有关详细信息,请参阅源代码中的文件头。
历史
- 2008-10-11 - 初始版本。
- 2008-10-19 - 添加了与 CAB
EventBroker
的比较,添加了指向 Sourceforge.net 的链接。 - 2008-10-26 - 添加了直接在 EventBroker 上触发事件主题部分。