程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .NET陷阱 四 事件監聽帶來的問題與弱監聽器

.NET陷阱 四 事件監聽帶來的問題與弱監聽器

編輯:關於.NET

大家可能都遇到過沒有取消事件監聽而帶來的一些問題,像內存洩露、訪問無效數據等。當我們寫下如下代碼時:

source.StateChanged += observer.SourceStateChangedHandler

實際上source會保持有對observer的一個引用,所以如果source的生命期長於observer的話,則當其它地方不引用observer時,如果不顯示解除監聽,則observer不會被垃圾回收。這可能會帶來兩個問題:其一,如果observer占用了大量內存的話,則這部分內存不會被釋放;其二,程序的其它地方可能已經處於不一致的狀態,這樣當source.StateChanged事件再次發生時,observer.SourceStateChanged方法仍然會被調用,而此方法內部的邏輯可能會造成異常。

當然最直接的辦法是在不使用observer時顯示解除監聽,像下面那樣:

source.StateChanged -= observer.SourceStateChangedHandler

但程序員經常會忘記這一點。所以便有了“弱事件監聽器”的概念,我們期望在監聽時多做些工作,然後能達到自動取消監聽的目的。廢話不說,先上代碼。

/// <summary>   
 /// 當弱監聽器發現被包裝的監聽者已經被垃圾收集時所調用的委托。   
 /// </summary>   
 /// <typeparam name="E">事件參數類型。</typeparam>   
 /// <param name="handler">MakeWeak方法返回的事件處理函數,提供此委托的地方   
 /// 要負責把此對象從被監聽對象的事件處理方法列表中移除。</param>   
 /// <param name="param">在調用MakeWeak方法時傳入的額外參數。</param>   
 public delegate void UnregisterCallback<E>(EventHandler<E> handler, object param) where E : EventArgs;   
        
 /// <summary>   
 /// 當進行事件處理時,如果被監聽對象的生命期比監聽器的生命周期長,我們就必   
 /// 須在監聽器的生命期內取消對被監聽對象的事件監聽,否則被監聽對象會持有監   
 /// 聽器的一個強引用,而阻止它被垃圾收集。但有時我們經常忘記取消事件監聽,   
 /// 或者不容易確定何時解除監聽。此時可以使用弱監聽器,把如下代碼:   
 /// <code>   
 /// observed.SomeEvent += observer.SomeEventHandler;   
 /// </code>   
 /// 改為:   
 /// <code>   
 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(   
 ///     observer.SomeEventHandler,    
 ///     (handler, param) => observed.SomeEvent -= handler,    
 ///     null);   
 /// </code>   
 /// 上面的代碼使用了lambda表達式以捕獲變量,也可以像下面那樣使用param參數:   
 /// <code>   
 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(   
 ///     observer.SomeEventHandler,    
 ///     OnUnregisterWeakEvent,    
 ///     observed);   
 ///    
 /// void OnUnregisterWeakEvent(EventHandler&lt;E&gt; handler, object param)   
 /// {   
 ///     ((ObservedType)param).SomeEvent -= handler;   
 /// }   
 /// </code>   
 /// 或者使用如下形式:   
 /// <code>   
 /// observed.SomeEvent += WeakEventHandlerFactory.MakeWeak2(   
 ///     observer.SomeEventHandler, observed, "SomeEvent");   
 /// </code>   
 /// 其中MakeWeak的第二個參數將弱監聽器從事件源中移除。即使將第二個參數指定   
 /// 為null,也不會阻止observer對象被垃圾收集,但事件源中將始終保持一個輕量   
 /// 對象的引用。    
 /// </summary>   
 public static class WeakEventHandlerFactory   
 {   
     /// <summary>   
     /// 我們在MakeWeak方法中使用反射創建WeakEventHandler的實例,所以在(1)   
     /// 處理無法指定泛型參數T,以完成轉換,此接口用於簡化這一步驟。   
     /// </summary>   
     /// <typeparam name="E">事件參數類型。</typeparam>   
     private interface IWeakEventHandler<E> where E : EventArgs   
     {   
         /// <summary>   
         /// 事件處理器。   
         /// </summary>   
         EventHandler<E> Handler   
         {   
             get;   
         }   
     }   
        
     /// <summary>   
     /// 對指定的事件處理函數創建一個弱監聽器。   
     /// </summary>   
     /// <typeparam name="E">事件參數類型。</typeparam>   
     /// <param name="handler">被包裝的事件處理器。</param>   
     /// <param name="unregister">用於將弱監聽器從事件源中移除的委托。可以指   
     /// 定為null,這時事件源中將始終保持一個輕量對象的引用,但不會阻止被包   
     /// 裝的對象被垃圾收集。</param>   
     /// <param name="param">在調用unregister時使用的額外參數,可以是null。</param>   
     /// <returns>生成的弱監聽器。</returns>   
     public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler,   
         UnregisterCallback<E> unregister, object param) where E : EventArgs   
     {   
         if (handler == null)   
         {   
             throw new ArgumentNullException("handler");   
         }   
        
         if (handler.Method.IsStatic || handler.Target == null)   
         {   
             throw new ArgumentException("Only instance methods are supported.", "handler");   
         }   
        
         var type = typeof(WeakEventHandler<,>).MakeGenericType(   
             handler.Method.DeclaringType, typeof(E));   
        
         var wehConstructor = type.GetConstructor( new[]   
         {    
             typeof(EventHandler<E>),    
             typeof(UnregisterCallback<E>),   
             typeof(object)   
         });   
        
         // (1)   
         var weak = (IWeakEventHandler<E>)wehConstructor.Invoke(   
             new [] { handler, unregister, param });   
         return weak.Handler;   
     }   
        
     /// <summary>   
     /// 此方法相當於MakeWeak(handler, unregister, null)。   
     /// </summary>   
     public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler,   
         UnregisterCallback<E> unregister) where E : EventArgs   
     {   
         return MakeWeak(handler, unregister, (object)null);   
     }   
        
     /// <summary>   
     /// 使用CreateUnregisterCallback創建取消弱監聽器委托的形式注冊監聽器。   
     /// </summary>   
     /// <typeparam name="E">事件參數類型。</typeparam>   
     /// <param name="handler">被包裝的事件處理器。</param>   
     /// <param name="observed">被監聽的對象。</param>   
     /// <param name="eventName">監聽的事件名稱。</param>   
     /// <returns>生成的弱監聽器。</returns>   
     public static EventHandler<E> MakeWeak2<E>(EventHandler<E> handler,   
         object observed, string eventName) where E : EventArgs   
     {   
         return MakeWeak(handler, CreateUnregisterCallback<E>(observed, eventName));   
     }   
        
     /// <summary>   
     /// 創建一個用於取消弱監聽器注冊的委托。   
     /// </summary>   
     /// <typeparam name="E">事件參數類型。</typeparam>   
     /// <param name="observed">被監聽的對象。</param>   
     /// <param name="eventName">監聽的事件名稱。</param>   
     /// <returns>創建結果,不會是null。</returns>   
     public static UnregisterCallback<E> CreateUnregisterCallback<E>(   
         object observed, string eventName) where E : EventArgs   
     {   
         return new UnregisterHelper<E>(observed, eventName).Callback;   
     }   
        
     /// <summary>   
     /// 用於將弱監聽器從事件源中移除的輔助類,在C++/CLI等不支持lambda表示式   
     /// 和自動委托的語言中,使用弱監聽器的語法可能很復雜,此類用於簡化這種   
     /// 情況。   
     /// </summary>   
     /// <typeparam name="E">委托事件參數類型。</typeparam>   
     private class UnregisterHelper<E> where E : EventArgs   
     {   
         /// <summary>   
         /// 被監聽的對象。   
         /// </summary>   
         private readonly object observed;   
        
         /// <summary>   
         /// 事件名稱。   
         /// </summary>   
         private readonly string eventName;   
        
         /// <summary>   
         /// 構造函數。   
         /// </summary>   
         internal UnregisterHelper(object observed, string eventName)   
         {   
             this.observed = observed;   
             this.eventName = eventName;   
         }   
        
         /// <summary>   
         /// 用於取消監聽的委托。   
         /// </summary>   
         internal UnregisterCallback<E> Callback   
         {   
             get   
             {   
                 return (handler, param) =>   
                 {   
                     var info = observed.GetType().GetEvent(eventName);   
                     info.RemoveEventHandler(observed, handler);   
                 };   
             }   
         }   
     }   
        
     /// <summary>   
     /// 弱事件監聽器。   
     /// </summary>   
     /// <typeparam name="T">監聽者的類型。</typeparam>   
     /// <typeparam name="E">事件參數類型。</typeparam>   
     private class WeakEventHandler<T, E> : IWeakEventHandler<E>   
         where T : class where E : EventArgs   
     {   
        /// <summary>   
         /// 對監聽器的弱引用。   
         /// </summary>   
         private readonly WeakReference weakReference;   
        
         /// <summary>   
         /// 用於調用被包裝的監聽器的委托。   
         /// </summary>   
         private readonly OpenEventHandler openHandler;   
        
         /// <summary>   
         /// 調用unregister時的額外參數。   
         /// </summary>   
         private readonly object param;   
        
         /// <summary>   
         /// 監聽器移除委托。   
         /// </summary>   
         private UnregisterCallback<E> unregister;   
        
         /// <summary>   
         /// 構造函數。   
         /// </summary>   
         /// <param name="handler">被包裝的事件處理器。</param>   
         /// <param name="unregister">用於移除弱監聽器的代碼。</param>   
         /// <param name="param">調用unregister時的額外參數。</param>   
         public WeakEventHandler(EventHandler<E> handler,   
             UnregisterCallback<E> unregister, object param)   
         {   
             weakReference = new WeakReference(handler.Target);   
             openHandler = (OpenEventHandler)Delegate.CreateDelegate(   
                 typeof(OpenEventHandler), null, handler.Method);   
             Handler = Invoke;   
             this.unregister = unregister;   
             this.param = param;   
         }   
        
         /// <summary>   
         /// 包裝監聽器事件處理函數的開放委托類型。   
         /// </summary>   
         private delegate void OpenEventHandler(T @this, object sender, E e);   
        
         /// <summary>   
         /// <see>IWeakEventHandler.Handler</see>   
         /// </summary>   
         public EventHandler<E> Handler   
         {   
             get;   
             private set;   
         }   
        
         /// <summary>   
         /// 弱監聽器事件處理函數。   
         /// </summary>   
         /// <param name="sender">引發事件的對象。</param>   
         /// <param name="e">事件參數。</param>   
         private void Invoke(object sender, E e)   
         {   
             T target = (T)weakReference.Target;   
             if (target != null)   
             {   
                 openHandler.Invoke(target, sender, e);   
             }   
             else if (unregister != null)   
             {   
                 unregister(Handler, param);   
                 unregister = null;   
             }   
         }   
     }   
 }

此工具的具體使用方法可以參考WeakEventFactory的注釋。這樣通過在添加監聽時多寫一些代碼,就可以避免忘記取消監聽或者不知道在什麼地方取消監聽所帶來的問題了。其中第二種方法可以用於C++/CLI中,再加上一些宏定義就非常簡潔了。

observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(   
    observer.SomeEventHandler,    
    (handler, param) => observed.SomeEvent -= handler,    
    null);   
        
observed.SomeEvent += WeakEventHandlerFactory.MakeWeak2(   
    observer.SomeEventHandler, observed, "SomeEvent");

PS: 弱監聽器和上面WeakEventHandlerFactory代碼的思想是我在解決此問題時從網上找到的,但沒有記下具體的網址,所以這裡不能提供相應的鏈接。大家可以自行在google中搜索weak event listener。

出處:http://www.cnblogs.com/brucebi/archive/2013/04/08/3008619.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved