1. 程式人生 > 實用技巧 >EventBroker:同步和非同步通知元件,鬆散耦合的事件處理

EventBroker:同步和非同步通知元件,鬆散耦合的事件處理

Please see the Download section below for instructions on how to run the code.

前言 本文是對Daniel Grunwald的文章《c#中的弱事件》的迴應。 介紹 EventBroker是一個可以用於在系統中觸發和接收通知的元件。 特性 鬆耦合:訂閱者不必知道釋出者。它們都只需要知道EventTopic URI(系統中唯一標識EventTopic的字串)。這有助於構建鬆散耦合的系統。 執行緒同步:訂閱者定義在哪個執行緒上執行訂閱處理程式: 與釋出伺服器(同步)後臺執行緒(非同步)相同的執行緒使用者介面執行緒(同步或非同步) 釋出者可以將訂閱限制為同步或非同步的事件。 多個釋出者/訂閱者:多個釋出者/訂閱者可以觸發/處理同一個事件主題。 弱引用:弱引用引用釋出者和訂閱者,這不會阻止它們被垃圾收集——就像在正常的事件註冊模型中一樣。 範圍: 每個EventBroker的作用域:在觸發事件時,只有註冊在同一個EventBroker上的釋出者和訂閱者被連線在一起。通常,您將在系統中使用一個EventBroker來處理所有事件通知。但是,在特殊情況下,為子系統定義一個新的EventBroker是有用的。這使您可以為事件通知定義範圍。 具有層次命名的作用域:釋出者和訂閱者可以以層次方式命名,事件可以是全域性的,只對父事件或只對子事件。釋出者和訂閱者都可以定義它們釋出/接收的範圍。 背景 這個EventBroker基於來自Microsoft的複合(UI)應用程式塊的EventBroker。請參閱下面與CAB EventBroker的比較小節,以瞭解不同之處。 使用的程式碼 樣本的出版商 釋出事件主題: 隱藏,複製Code

public class Publisher
{
    [EventPublication("topic://EventBrokerSample/SimpleEvent")]
    public event EventHandler SimpleEvent;
    
    ///<summary>Fires the SimpleEvent</summary>
    public void CallSimpleEvent()
    {
        SimpleEvent(this, EventArgs.Empty);
    }
}

向事件代理註冊釋出者(您必須在程式碼中儲存事件代理的例項)。示例假設有一個為我們儲存事件代理例項的服務: 隱藏,複製Code

EventBroker eb = Service.EventBroker;
Publisher p = new Publisher();
eb.Register(p);

在註冊釋出者時,事件代理檢查釋出者是否釋出了事件(具有EventPublication屬性的事件)。 示例使用者 訂閱一個事件主題: 隱藏,複製Code

public class Subscriber
{
    [EventSubscription(
        "topic://EventBrokerSample/SimpleEvent", 
        typeof(Handlers.Publisher))]
    public
void SimpleEvent(object sender, EventArgs e) { // do something useful or at least funny } }

向事件代理註冊訂閱者: 隱藏,複製Code

EventBroker eb = Service.EventBroker; 
Subscriber s = new Subscriber();
eb.Register(s); 

事件代理將在註冊時檢查訂閱伺服器是否訂閱了事件主題(具有EventSubscription屬性的方法)。 如果釋出者為已註冊的訂閱者觸發事件主題,則事件代理將通過使用釋出者用於觸發其事件的sender和Eventargs呼叫訂閱處理程式方法將其轉發給訂閱者。 出版選項 簡單的 隱藏,複製Code

[EventPublication("Simple")]
public event EventHandler SimpleEvent;

使用自定義的Eventargs 注意:CustomEventArgs只能從EventArgs派生。 隱藏,複製Code

[EventPublication("CustomEventArgs")]
public event EventHandler<CustomEventArguments> CustomEventArgs;

使用單個事件釋出多個事件主題 隱藏,複製Code

[EventPublication("Event1")]
[EventPublication("Event2")]
[EventPublication("Event3")]
public event EventHandler MultiplePublicationTopics;

只允許同步訂閱處理程式 有關更多細節,請參閱與CAB EventBroker/訂閱處理程式限制的比較部分。 隱藏,複製Code

[EventPublication("test", HandlerRestriction.Synchronous)]
public event EventHandler AnEvent; 

只允許非同步訂閱處理程式 有關更多細節,請參閱與CAB EventBroker/訂閱處理程式限制的比較一節。 隱藏,複製Code

[EventPublication("test", HandlerRestriction.Asynchronous)]
public event EventHandler AnEvent;

訂閱選項 簡單的 隱藏,複製Code

[EventSubscription("Simple", typeof(Handlers.Publisher)]
public void SimpleEvent(object sender, EventArgs e) {} 

定製的Eventargs 隱藏,複製Code

[EventSubscription("CustomEventArgs"), typeof(Handlers.Publisher))]
public void CustomEventArgs(object sender, CustomEventArgs e) {} 

訂閱多個事件主題 隱藏,複製Code

[EventSubscription("Event1", typeof(Handlers.Publisher))]
[EventSubscription("Event2", typeof(Handlers.Publisher))]
[EventSubscription("Event3", typeof(Handlers.Publisher))]
public void MultipleSubscriptionTopics(object sender, EventArgs e) {} 

在後臺執行緒上執行處理器(非同步) 事件代理建立一個工作執行緒來執行處理程式方法。釋出者可以立即繼續處理。 隱藏,複製Code

[EventSubscription("Background", typeof(Handlers.Background))]
public void BackgroundThread(object sender, EventArgs e) {} 

在UI執行緒上執行處理器 如果從後臺工作執行緒呼叫更新使用者介面的使用者介面元件,則使用此選項—不再需要Control.Invoke(…)。 隱藏,複製Code

[EventSubscription("UI", typeof(Handlers.UserInterface))]
public void UI(object sender, EventArgs e) {}

注意,如果使用UserInterface處理程式,那麼必須確保在使用者介面執行緒上註冊了訂閱者。否則,EventBroker將無法切換到使用者介面執行緒,並將丟擲異常。 在UI執行緒上非同步執行處理程式 與上面相同,但在訂閱伺服器處理完事件之前,不會阻塞釋出伺服器。 隱藏,複製Code

[EventSubscription("UIAsync", typeof(Handlers.UserInterfaceAsync))]
public void UI(object sender, EventArgs e) {}  

直接在EventBroker上觸發事件主題 事件主題可以直接在EventBroker上觸發,而不需要註冊釋出者。當您需要從只存在很短時間的物件觸發事件主題時,這就很方便了。 這種場景的一個很好的示例是計劃作業執行。在計劃時間,將例項化並執行作業類的例項。註冊它會很麻煩例項,觸發事件,然後再次取消註冊-直接在EventBroker上觸發事件要容易得多: 隱藏,複製Code

eventBroker.Fire("topic", sender, eventArgs);

範圍 有時,有必要限制釋出事件的範圍。這可以通過兩種不同的方式實現: 事件代理的多個例項 訂閱者只能監聽註冊在同一事件代理上的釋出者的事件。因此,事件代理會自動構建範圍。 如果應用程式中有多個事件處理範圍,這是最簡單的解決方案,應該始終是首選方案。但是,有時您需要在單個事件代理上對物件的作用域進行更多的控制。下一節將描述此場景。 分層命名 釋出者和訂閱者可以通過實現INamedItem介面來命名。這個介面提供了一個屬性: 隱藏,複製Code

string EventBrokerItemName { get; }

這允許在事件代理中標識物件,而釋出和訂閱屬性始終繫結到類。 命名採用與名稱空間相同的模式進行分層: Test是Test. mypublisher1test的父類。MyName1是Test.MyName2Test.MyName1的兄弟姐妹。子名稱是Test.MyName1Test的子元素。MyName1是Test的孿生兄弟。MyName1(名稱相同的兩個物件是雙胞胎) 現在,釋出者可以定義它想要釋出事件的範圍,方法是在publication屬性中定義一個範圍: 隱藏,複製Code

[EventPublication("Topic")]
public event EventHandler PublishedGlobally;

[EventPublication("Topic", typeof(ScopeMatchers.PublishToParents)]
public event EventHandler PublishedToParentsAndTwinsOnly;

[EventPublication("Topic", typeof(ScopeMatchers.PublishToChildren)]
public event EventHandler PublishedToChildrenAndTwinsOnly;

第一個事件是所有訂閱者都可以接收的全域性事件。第二個事件僅傳遞給釋出者的父或孿生訂閱者。第三個事件僅傳遞給與釋出者相鄰或相鄰的訂閱者。 訂閱者可以定義它想要接收事件的範圍: 隱藏,複製Code

[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的實踐和模式組CAB(複合UI應用程式塊)的EventBroker。 本節描述它們的區別。 獨立的 bbv。Common EventBroker可以獨立使用。您可以在專案中任何需要通知的地方使用它,而不像CAB要求的那樣有任何框架約束。 開發人員指南 我嘗試以一種能儘快看到錯誤的方式實現EventBroker。我會給你一些例子來說明它的含義: 如果已釋出事件提供的EventHandler型別與訂閱處理程式提供的簽名不匹配,則會在註冊時而不是在事件觸發時丟擲異常。如果你使用UserInterface或UserInterfaceAsync訂閱處理程式,那麼一個異常丟擲在註冊時,如果沒有WindowsFormsSynchronizationContext是存在的,這意味著當前執行緒不是使用者介面執行緒。這將導致在處理事件時出現跨執行緒異常。 訂閱處理程式的限制 注意:這個特性只在Sourceforge.net上的版本中可用(參見下載部分),而在附加的解決方案中不可用(它太新了;-))。 我們遇到的問題是,一些釋出者必須確保所有訂閱者同步地處理其事件。例如,如果您釋出了一個取消事件,則為所有訂閱者提供了一種取消當前操作的方法。只有當沒有訂閱者將CancelEventArgs上的Cancel屬性設定為true時,釋出者才能繼續其操作。因此,釋出者必須將此事件上的所有訂閱限制為同步: 隱藏,複製Code

[EventPublication("test", HandlerRestriction.Synchronous)]
public event EventHandler AnEvent;

如果訂閱者為此事件註冊非同步處理程式,則丟擲異常。注意,異常是在註冊時丟擲的,而不是在觸發事件時丟擲的。這大大簡化了一致程式碼的編寫。 此外,釋出者可以將訂閱處理程式限制為非同步的,因為釋出者不想被阻塞: 隱藏,複製Code

[EventPublication("test", HandlerRestriction.Asynchronous)]
public event EventHandler AnEvent;

日誌記錄 bbv的事件代理。Common在日誌訊息方面非常健談。這使您能夠了解發布者何時觸發事件、如何將事件路由到訂閱者、如何處理事件以及發生了哪些異常。 注意:bbv。通常使用log4net記錄訊息。這意味著,您可以為每個元件配置記錄的訊息級別。 可擴充套件性 ThreadOption——比;處理程式 我用可擴充套件的處理程式替換了enum ThreadOption來定義事件的處理方式(同步)hronously非同步)。 範圍——比;匹配器範圍 您可以實現自己的範圍匹配器來提供符合您需要的事件層次結構,而不是使用列舉指定範圍。 內部 我們將在本文的下一個更新中瞭解其內部原理。在此之前,請參閱下載中提供的原始碼。 下載 本文頂部的下載包含三個Visual Studio 2008專案: bbv.Common。事件代理元件。bbv. common .EventBroker。測試:單元測試(NUnit).bbv. common . eventbroker。示例:一個小示例應用程式(事件代理的基本用法)。 bbv.Common。EventBroker是一個更大的庫的一部分,其中包含一些其他很酷的元件,這些元件可以在這裡找到。 這個下載只是一個精簡版。要試用它,請開啟解決方案,將啟動專案設定為bb . common . eventbroker。取樣,然後按F5。 請注意,本文附帶的版本並不是最新版本。請務必檢視Sourceforge頁面以瞭解最新版本。 許可證 注意,bbc . common。EventBroker是在Apache License 2.0下許可的,但是因為EventBroker包含來自Microsoft的CAB的部分,所以必須應用額外的許可限制,詳細資訊請參閱原始碼中的檔案頭。 歷史 更新:閱讀:初始版本。更新:閱讀:添加了與CAB EventBroker的比較,添加了sourceforge.net的連結。 本文轉載於:http://www.diyabc.com/frontweb/news1995.html