事件聚合器
Prism库提供了一种事件机制,可以在应用程序中的松散耦合组件之间进行通信。这种基于事件聚合器服务的机制允许发布者和订阅者通过事件进行通信,并且彼此之间仍然没有直接引用。
EventAggregator
提供发布/订阅多播的功能。这意味着可以有多个发布者发起同一个事件,也可以有多个订阅者监听同一个事件。您也可以考虑使用EventAggregator
来跨模块发布事件,以及在业务逻辑代码(例如Controllers和Presenters)之间发送消息。
使用Prism库创建的事件是类型化的事件。这意味着您可以在运行应用程序之前利用编译时的类型检查来发现错误。在Prism库中,EventAggregator
允许订阅者或发布者使用特定的EventBase
进行定位,并且事件聚合器还允许多个发布者和多个订阅者,如下图所示。
IEventAggregator
EventAggregator
类在容器中作为服务提供,可以通过IEventAggregator
接口进行检索。事件聚合器负责定位或构建事件,并在系统中维持事件的集合。
public interface IEventAggregator
{
TEventType GetEvent<TEventType>() where TEventType : EventBase;
}
如果还没有构造事件,则EventAggregator
会在第一次访问时构造事件,这使发布者或订阅者无需确定事件是否可用。
PubSubEvent
连接发布者和订阅者的真正工作是由PubSubEvent
类完成的。 这是Prism库中包含EventBase
类的唯一实现。此类维护订阅者列表并处理向订阅者发送的事件。
PubSubEvent
类是一个泛型类,需要将负载类型定义为泛型类型。这有助于在编译时强制发布者和订阅者提供正确的方法来成功连接事件。以下代码显示了PubSubEvent类的部分定义。
[!注意]
PubSubEvent
可以在Prism.Events中找到,其位于Prism.Core NuGet包中。
创建一个事件
PubSubEvent<TPayload>
目的是成为应用程序或模块特定事件的基类。TPayLoad
是事件负载的类型,其中负载是发布事件时将传递给订阅者的参数。
例如,下面的代码展示了TickerSymbolSelectedEvent
类。其中负载是一个包含公司符号的字符串,并且注意这个类的实现是空的。
public class TickerSymbolSelectedEvent : PubSubEvent<string>{}
[!注意] 在复合应用程序中,事件经常在多个模块之间共享,因此它们会被定义在公共位置。通常的做法是在共享程序集中定义这些事件,例如"Core"或"Infrastructure"项目。
发布事件
发布者通过从EventAggregator
中检索事件并调用Publish
方法来引发事件。要访问EventAggregator
,您可以通过在类的构造函数中添加一个IEventAggregator
类型的参数来使用依赖注入的值。
public class MainPageViewModel
{
IEventAggregator _eventAggregator;
public MainPageViewModel(IEventAggregator ea)
{
_eventAggregator = ea;
}
}
以下代码演示如何发布TickerSymbolSelectedEvent。
_eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish("STOCK0");
订阅事件
订阅者可以使用PubSubEvent
类中提供的Subscribe
方法重载之一来订阅一个事件。
public class MainPageViewModel
{
public MainPageViewModel(IEventAggregator ea)
{
ea.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews);
}
void ShowNews(string companySymbol)
{
//implement logic
}
}
有几种方式可以订阅PubSubEvents
。使用以下标准来帮助您确定哪个选项最适合您的需求:
- 如果需要在收到事件时更新UI元素,请在UI线程上订阅接收事件。
- 如果您需要筛选事件,请在订阅时提供筛选委托。
- 如果您对事件的性能有顾虑,请考虑在订阅时使用强引用委托,然后手动取消订阅PubSubEvent。
- 如果上述任何一项都不适用,请使用默认订阅。
下面几节将描述这些选项。
在UI线程上订阅事件
通常情况下,订阅者需要更新UI元素以响应事件。而在WPF中,只有UI线程才可以更新UI元素。
默认情况下,订阅者会在发布者的线程上接收事件。即如果发布者从UI线程上发送事件,则订阅者可以更新UI元素。但是如果发布者的线程是后台线程,订阅者可能无法直接更新UI元素。在这种情况下,订阅者需要使用Dispatcher类以在UI线程上调度更新UI元素。
Prism库提供的PubSubEvent
可以帮助订阅者在UI线程上自动接收事件。订阅者在订阅期间也表现出了这一点,如以下代码示例所示。
public class MainPageViewModel
{
public MainPageViewModel(IEventAggregator ea)
{
ea.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews, ThreadOption.UIThread);
}
void ShowNews(string companySymbol)
{
//implement logic
}
}
ThreadOption
有以下选项可用:
PublisherThread
:使用此设置接收发布者线程上的事件,本选项是默认设置。BackgroundThread
:使用此设置在.NET Framework线程池的线程上异步接收事件。UIThread
:使用此设置接收UI线程上的事件。
[!注意] 为了让
PubSubEvent
在UI线程上发布给订阅者,必须首先在UI线程上构建EventAggregator
。
使用过滤器过滤订阅事件
订阅者可能不需要处理已发布事件的每个命令。在这些情况下,订阅者可以使用参数过滤器。参数过滤器的类型是System.Predicate<TPayLoad>
,是一个在发布事件时执行以确定已发布事件的负载是否符合调用订阅者回调所需的一组条件的委托。如果负载不满足指定条件,则不执行订阅者回调。
通常,此过滤器作为lambda表达式提供,如以下代码示例所示。
public class MainPageViewModel
{
public MainPageViewModel(IEventAggregator ea)
{
TickerSymbolSelectedEvent tickerEvent = ea.GetEvent<TickerSymbolSelectedEvent>();
tickerEvent.Subscribe(ShowNews, ThreadOption.UIThread, false,
companySymbol => companySymbol == "STOCK0");
}
void ShowNews(string companySymbol)
{
//implement logic
}
}
[!注意]
Subscribe
方法返回类型为Prism.Events.SubscriptionToken
的订阅令牌,稍后可用于删除对事件的订阅。当您在使用匿名委托或lambda表达式作为回调委托时,或者当您使用不同的过滤器订阅同一的事件处理程序时,此令牌特别有用。
[!注意] 不建议从回调委托中修改负载对象,因为可能有多个线程同时访问负载对象。您可以将负载设置为不可变的,以避免并发错误。
使用强引用订阅事件
如果您在短时间内引发多个事件,并注意到它们存在性能问题,则您可能需要订阅强委托引用。不过如果您这样做,那么在处理订阅者时将需要手动从事件取消订阅。
默认情况下,PubSubEvent
维持一个对订阅者处理程序和过滤器的弱委托引用,这意味着PubSubEvent
持有的引用不会阻止订阅者的垃圾回收。而且使用弱委托引用使订阅者无需取消订阅,并允许其进行适当的垃圾回收。
但是,维持弱委托引用比相应的强引用要慢。不过对于大多数应用程序,这种性能差异不会很明显,但是如果您的应用程序会在短时间内发布大量事件,您可能还是需要使用 PubSubEvent的强引用。如果您确实使用了强委托引用,那您的订阅者应该自行取消订阅,以便在您的订阅对象不再使用时启用正确的垃圾回收。
要使用强引用进行订阅,请在Subscribe
方法中使用keepSubscriberReferenceAlive
参数,如以下代码示例所示。
public class MainPageViewModel
{
public MainPageViewModel(IEventAggregator ea)
{
bool keepSubscriberReferenceAlive = true;
TickerSymbolSelectedEvent tickerEvent = ea.GetEvent<TickerSymbolSelectedEvent>();
tickerEvent.Subscribe(ShowNews, ThreadOption.UIThread, keepSubscriberReferenceAlive,
companySymbol => companySymbol == "STOCK0");
}
void ShowNews(string companySymbol)
{
//implement logic
}
}
keepSubscriberReferenceAlive
参数的类型为bool
:
- 当设置为
true
时,事件实例保持对订阅者实例的强引用,因此不允许它被垃圾回收。有关如何取消订阅的信息,请参阅本主题后面的取消订阅事件一节。 - 当设置为
false
(省略此参数时的默认值)时,事件实例保持对订阅者实例的弱引用,从而允许垃圾收集器在没有其他引用时处理订阅者实例。当收集到订阅者实例时,事件将自动取消订阅。
取消订阅事件
如果您的订阅者不再希望接收事件,您可以使用订阅者的处理程序取消订阅,或者使用订阅令牌取消订阅。
以下代码示例显示了如何直接取消订阅处理程序。
public class MainPageViewModel
{
TickerSymbolSelectedEvent _event;
public MainPageViewModel(IEventAggregator ea)
{
_event = ea.GetEvent<TickerSymbolSelectedEvent>();
_event.Subscribe(ShowNews);
}
void Unsubscribe()
{
_event.Unsubscribe(ShowNews);
}
void ShowNews(string companySymbol)
{
//implement logic
}
}
下面的代码示例显示如何使用订阅令牌取消订阅。令牌会作为Subscribe
方法的返回值提供。
public class MainPageViewModel
{
TickerSymbolSelectedEvent _event;
SubscriptionToken _token;
public MainPageViewModel(IEventAggregator ea)
{
_event = ea.GetEvent<TickerSymbolSelectedEvent>();
_token = _event.Subscribe(ShowNews);
}
void Unsubscribe()
{
_event.Unsubscribe(_token);
}
void ShowNews(string companySymbol)
{
//implement logic
}
}