国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C# > 正文

詳解C#編程中.NET的弱事件模式

2020-01-24 01:38:04
字體:
供稿:網(wǎng)友

 引言

你可能知道,事件處理是內(nèi)存泄漏的一個常見來源,它由不再使用的對象存留產(chǎn)生,你也許認(rèn)為它們應(yīng)該已經(jīng)被回收了,但不是,并有充分的理由。

在這個短文中(期望如此),我會在 .Net 框架的上下文事件處理中展示這個問題,之后我會教你這個問題的標(biāo)準(zhǔn)解決方案,弱事件模式。有兩種方法,即:

  •     “傳統(tǒng)”方法 (嗯,在 .Net 4.5 前,所以也沒那么老),它實現(xiàn)起來比較繁瑣
  •     .Net 4.5 框架提供的新方法,它則是盡其可能的簡單

(源代碼在 這里 可供使用。)

從常見事物開始

在一頭扎進本文核心內(nèi)容前,讓我們回顧一下在代碼中最常使用的兩個事物:類和方法。
事件源

讓我為您介紹一個基本但很有用的事件源類,它最低限度地揭示了足夠的復(fù)雜性來說明這一點:

 

public class EventSource{  public event EventHandlerEvent = delegate { };   public void Raise()  {    Event(this, EventArgs.Empty);  }}

對好奇那個奇怪的空委托初始化方法(delegate { })的人來說,這是一個用來確保事件總被初始化的技巧,這樣就可以不必每次在使用它之前都要檢查它是否不為NULL。

觸發(fā)垃圾收集的實用方法

在.net中,垃圾收集以一種不確定的方式觸發(fā)。這對我們的實驗很不利,我們的實驗需要以一種確定的方式跟蹤對象的狀態(tài)。

所以,我們必須定期觸發(fā)自己的垃圾收集操作,同時避免復(fù)制管道代碼,管道代碼已經(jīng)在在一個特定的方法中釋放:
 

static void TriggerGC(){  Console.WriteLine("Starting GC.");   GC.Collect();  GC.WaitForPendingFinalizers();  GC.Collect();   Console.WriteLine("GC finished.");}

雖然不是很復(fù)雜,但是如果你不是很熟悉這種模式,還是有必要小小解釋一下:

  •     第一個 GC.Collect() 觸發(fā).net的CLR垃圾收集器,對于負(fù)責(zé)清理不再使用的對象,和那些類中沒有終結(jié)器(即c#中的析構(gòu)函數(shù))的對象,CLR垃圾收集器足夠勝任
  •     GC.WaitForPendingFinalizers() 等待其他對象的終結(jié)器執(zhí)行;我們需要這樣做,因為,你將看到我們使用終結(jié)器方法去追蹤我們的對象在什么時候被收集的
  •     第二個GC.Collect() 確保新生成的對象也被清理了

引入問題

首先讓我們試著通過一些理論,最重要的是還有一個演示的幫助,去了解事件監(jiān)聽器有哪些問題。
背景

一個對象要想被作為事件偵聽器,需要將其實例方法之一登記為另一個能夠產(chǎn)生事件的對象(即事件源)的事件處理程序,事件源必須保持一個到事件偵聽器對象的引用,以便在事件發(fā)生時調(diào)用此偵聽器的處理方法。

這很合理,但如果這個引用是一個 強引用,則偵聽器會作為事件源的一個依賴 從而不能作為垃圾回收,即使引用它的最后一個對象是事件源。


下面詳細(xì)圖解在這下面發(fā)生了什么:

事件處理問題

這將不是一個問題,如果你可以控制listener object的生命周期,你可以取消對事件源的訂閱當(dāng)當(dāng)你不再需要listener,常常可以使用disposable pattern(用后就扔的模式)。

但是如果你不能在listener生命周期內(nèi)驗證單點響應(yīng),在確定性的方式中你不能把它處理掉,你必須依賴GC處理...這將從不會考慮你所準(zhǔn)備的對象,只要事件源還存在著!

例子

理論都是好的,但還是讓我們看看問題和真正的代碼。

這是我們勇敢的時間監(jiān)聽器,還有點幼稚,我們很快知道為什么:
 

public class NaiveEventListener{  private void OnEvent(object source, EventArgs args)  {    Console.WriteLine("EventListener received event.");  }   public NaiveEventListener(EventSource source)  {    source.Event += OnEvent;  }   ~NaiveEventListener()  {    Console.WriteLine("NaiveEventListener finalized.");  }}

用一個簡單例子來看看怎么實現(xiàn)運作:
 

Console.WriteLine("=== Naive listener (bad) ==="); EventSource source = new EventSource(); NaiveEventListener listener = new NaiveEventListener(source); source.Raise(); Console.WriteLine("Setting listener to null.");listener = null; TriggerGC(); source.Raise(); Console.WriteLine("Setting source to null.");source = null; TriggerGC();

輸出:

 

EventListener received event.Setting listener to null.Starting GC.GC finished.EventListener received event.Setting source to null.Starting GC.NaiveEventListener finalized.GC finished.

讓我們分析下這個運作流程:

  •     “EventListener received event.“:這是我們調(diào)用 “source.Raise()”的結(jié)果; perfect, seems like we're listening.
  •     “Setting listener to null.“: 我們把本地事件監(jiān)聽器對象引用賦空值,這樣應(yīng)該可以讓垃圾回收器回收了.
  •     “Starting GC.“: 垃圾回收開始.
  •     “GC finished.“: 垃圾回收開始, 但是 但是我們的事件監(jiān)聽器沒有被回收器回收, 這樣就證明了事件監(jiān)聽器的析構(gòu)函數(shù)沒有被調(diào)用。
  •     “EventListener received event.“: 第二次調(diào)用 “source.Raise()”來確認(rèn),發(fā)現(xiàn)這監(jiān)聽器還活著。
  •     “Setting source to null.“: 我們在賦空值給事件的原對象.
  •     “Starting GC.“: 第二次垃圾回收.
  •     “NaiveEventListener finalized.“: 這一次幼稚的事件監(jiān)聽終于被回收了,遲到總好過沒有.
  •     “GC finished.“:第二次垃圾回收完成.


結(jié)論:確實有一個隱藏的對事件監(jiān)聽器的強引用,目的是防止它在事件源被回收之前被回收!

希望有針對此問題的標(biāo)準(zhǔn)解決方案:讓事件源可以通過弱引用來引用偵聽器,在事件源存在時也可以回收偵聽器對象。

這里有一個標(biāo)準(zhǔn)的模式及其在.NET框架上的實現(xiàn):弱事件模式(http://msdn.microsoft.com/en-us/library/aa970850.aspx)。 And there is a standard pattern and its implementation in the .Net framework: the weak event pattern.

弱事件模式

讓我們看看在.NET中如何應(yīng)付這個問題,

通常有超過一種方法去做,但是在這種情況下可以直接決定:

  •     如果你正在使用 .Net 4.5 ,那么你將從簡單的實現(xiàn)受益
  •     另外,你必須依靠一點人為的技巧手段

傳統(tǒng)方式

  •     WeakEventManager 是所有模式管道的封裝
  •     IWeakEventListener 是管道,它允許一個組件連接到WeakEventManager管件

(這兩個位于WindowBase程序集,你將需要參考你自己的如果你不在開發(fā)WPF項目,你應(yīng)該準(zhǔn)確的參考WindowBase)


因此這有兩步處理.

首先通過繼承WeakEventManager來實現(xiàn)一個自定義事件管理器:

  •     重寫 StartListening 和 StopListening 方法,分別注冊一個新的handler和注銷一個已存在的; 它們將被WeakEventManager基類使用。
  •     提供兩個方法來訪問listener列表, 命名為 “AddListener” 和 “RemoveListener “,給自定義事件管理器的使用者使用。
  •     通過在自定義事件管理器上暴露一個靜態(tài)屬性,提供一個方式去獲得當(dāng)前線程的事件管理器。
  • 之后使listenr實現(xiàn)IWeakEventListenr接口:
  •     實現(xiàn) ReceiveWeakEvent 方法
  •     嘗試去處理這個事件
  •     如果無誤的處理好事件,將返回true


有很多要說的,但是可以相對地轉(zhuǎn)換成一些代碼:

首先是自定義弱事件管理器:
 

public class EventManager : WeakEventManager{  private static EventManager CurrentManager  {    get    {      EventManager manager = (EventManager)GetCurrentManager(typeof(EventManager));       if (manager == null)      {        manager = new EventManager();        SetCurrentManager(typeof(EventManager), manager);      }       return manager;    }  }    public static void AddListener(EventSource source, IWeakEventListener listener)  {    CurrentManager.ProtectedAddListener(source, listener);  }   public static void RemoveListener(EventSource source, IWeakEventListener listener)  {    CurrentManager.ProtectedRemoveListener(source, listener);  }   protected override void StartListening(object source)  {    ((EventSource)source).Event += DeliverEvent;  }   protected override void StopListening(object source)  {    ((EventSource)source).Event -= DeliverEvent;  }}

之后是事件listener:
 

public class LegacyWeakEventListener : IWeakEventListener{  private void OnEvent(object source, EventArgs args)  {    Console.WriteLine("LegacyWeakEventListener received event.");  }   public LegacyWeakEventListener(EventSource source)  {    EventManager.AddListener(source, this);  }   public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)  {    OnEvent(sender, e);     return true;  }   ~LegacyWeakEventListener()  {    Console.WriteLine("LegacyWeakEventListener finalized.");  }}

檢查下:
 

Console.WriteLine("=== Legacy weak listener (better) ==="); EventSource source = new EventSource(); LegacyWeakEventListener listener = new LegacyWeakEventListener(source); source.Raise(); Console.WriteLine("Setting listener to null.");listener = null; TriggerGC(); source.Raise(); Console.WriteLine("Setting source to null.");source = null; TriggerGC();

輸出:
 

LegacyWeakEventListener received event.Setting listener to null.Starting GC.LegacyWeakEventListener finalized.GC finished.Setting source to null.Starting GC.GC finished.

非常好,它起作用了,我們的事件listener對象現(xiàn)在可以在第一次GC里正確的析構(gòu),即使事件源對象還存活,不再泄露內(nèi)存了.

但是要寫一堆代碼就為了一個簡單的listener,想象一下你有一堆這樣的listener,你必須要為每個類型的寫一個弱事件管理器!

如果你很擅長代碼重構(gòu),你可以發(fā)現(xiàn)一個聰明的方式去重構(gòu)所有通用的代碼.

在.Net 4.5 出現(xiàn)之前,你必須自己實現(xiàn)弱事件管理器,但是現(xiàn)在,.Net提供一個標(biāo)準(zhǔn)的解決方案來解決這個問題了,現(xiàn)在就來回顧下吧!

 .Net 4.5 方式

.Net 4.5 已介紹了一個新的泛型版本的遺留WeakEventManager: WeakEventManager<TEventSource, TEventArgs>.

(這個類可以在WindowsBase集合.)

多虧了 .Net WeakEventManager<TEventSource, TEventArgs> 自己處理泛型, 不用去一個個實現(xiàn)新事件管理器.

而且代碼還簡單和可讀:
 

public class WeakEventListener{  private void OnEvent(object source, EventArgs args)  {    Console.WriteLine("WeakEventListener received event.");  }   public WeakEventListener(EventSource source)  {    WeakEventManager.AddHandler(source, "Event", OnEvent);  }   ~WeakEventListener()  {    Console.WriteLine("WeakEventListener finalized.");  }}

簡單的一行代碼,真簡潔.

其他實現(xiàn)的使用也是相似的, 就是裝入所有東西到事件listener類里:

 

Console.WriteLine("=== .Net 4.5 weak listener (best) ==="); EventSource source = new EventSource(); WeakEventListener listener = new WeakEventListener(source); source.Raise(); Console.WriteLine("Setting listener to null.");listener = null; TriggerGC(); source.Raise(); Console.WriteLine("Setting source to null.");source = null; TriggerGC();

輸出也是肯定正確的:
 

WeakEventListener received event.Setting listener to null.Starting GC.WeakEventListener finalized.GC finished.Setting source to null.Starting GC.GC finished.

預(yù)期結(jié)果也跟之前一樣,還有什么問題?!

結(jié)論

正如你看到的,在.Net上實現(xiàn)弱事件模式 是十分直接, 特別在 .Net 4.5.

如果你沒有用.Net 4.5來實現(xiàn),將需要一堆代碼, 你可能不去用任何模式而是直接使用C# (+= and -=), 看看是否有內(nèi)存問題,如果注意到泄露,還需要花必要的時間去實現(xiàn)一個。

但是用 .Net 4.5, 它是自由和簡潔,而且由框架管理, 你可以毫無顧慮的選擇它, 盡管沒有 C# 語法 “+=” 和 “-=” 的酷, 但是語義是清晰的,這才是最重要的.

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 红安县| 淮滨县| 大余县| 福鼎市| 盐池县| 浙江省| 巴楚县| 阿拉善右旗| 四川省| 阳江市| 岢岚县| 新密市| 睢宁县| 叶城县| 恩平市| 望都县| 甘孜县| 柯坪县| 新竹县| 定襄县| 驻马店市| 布拖县| 新竹市| 琼海市| 兴城市| 微博| 团风县| 辉县市| 洛宁县| 宁津县| 东乡| 宜兰县| 永嘉县| 安多县| 旬阳县| SHOW| 深州市| 达尔| 岫岩| 芮城县| 德阳市|