引言
你可能知道,事件處理是內(nèi)存泄漏的一個常見來源,它由不再使用的對象存留產(chǎn)生,你也許認(rèn)為它們應(yīng)該已經(jīng)被回收了,但不是,并有充分的理由。
在這個短文中(期望如此),我會在 .Net 框架的上下文事件處理中展示這個問題,之后我會教你這個問題的標(biāo)準(zhǔn)解決方案,弱事件模式。有兩種方法,即:
(源代碼在 這里 可供使用。)
從常見事物開始
在一頭扎進本文核心內(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ù)雜,但是如果你不是很熟悉這種模式,還是有必要小小解釋一下:
引入問題
首先讓我們試著通過一些理論,最重要的是還有一個演示的幫助,去了解事件監(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.
讓我們分析下這個運作流程:
結(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)付這個問題,
通常有超過一種方法去做,但是在這種情況下可以直接決定:
傳統(tǒng)方式
(這兩個位于WindowBase程序集,你將需要參考你自己的如果你不在開發(fā)WPF項目,你應(yīng)該準(zhǔn)確的參考WindowBase)
因此這有兩步處理.
首先通過繼承WeakEventManager來實現(xiàn)一個自定義事件管理器:
有很多要說的,但是可以相對地轉(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# 語法 “+=” 和 “-=” 的酷, 但是語義是清晰的,這才是最重要的.
新聞熱點
疑難解答
圖片精選