好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

.NET陷阱之四:事件监听带来的问题与弱监听器

.NET陷阱之四:事件监听带来的问题与弱监听器

.NET陷阱之四:事件监听带来的问题与弱监听器

大家可能都遇到过没有取消事件监听而带来的一些问题,像内存泄露、访问无效数据等。当我们写下如下代码时:

source.StateChanged += observer.SourceStateChangedHandler

实际上source会保持有对observer的一个引用,所以如果source的生命期长于observer的话,则当其它地方不引用observer时,如果不显示解除监听,则observer不会被垃圾回收。这可能会带来两个问题:其一,如果observer占用了大量内存的话,则这部分内存不会被释放;其二,程序的其它地方可能已经处于不一致的状态,这样当source.StateChanged事件再次发生时,observer.SourceStateChanged方法仍然会被调用,而此方法内部的逻辑可能会造成异常。

当然最直接的办法是在不使用observer时显示解除监听,像下面那样:

source.StateChanged -= observer.SourceStateChangedHandler

但程序员经常会忘记这一点。所以便有了“弱事件监听器”的概念,我们期望在监听时多做些工作,然后能达到自动取消监听的目的。废话不说,先上代码。 

   1   ///   <summary> 
   2   ///   当弱监听器发现被包装的监听者已经被垃圾收集时所调用的委托。
    3   ///   </summary> 
   4   ///   <typeparam name="E">  事件参数类型。  </typeparam> 
   5   ///   <param name="handler">  MakeWeak方法返回的事件处理函数,提供此委托的地方
    6   ///   要负责把此对象从被监听对象的事件处理方法列表中移除。  </param> 
   7   ///   <param name="param">  在调用MakeWeak方法时传入的额外参数。  </param> 
   8   public   delegate   void  UnregisterCallback<E>(EventHandler<E> handler,  object  param)  where   E : EventArgs;
    9  
  10   ///   <summary> 
  11   ///   当进行事件处理时,如果被监听对象的生命期比监听器的生命周期长,我们就必
   12   ///   须在监听器的生命期内取消对被监听对象的事件监听,否则被监听对象会持有监
   13   ///   听器的一个强引用,而阻止它被垃圾收集。但有时我们经常忘记取消事件监听,
   14   ///   或者不容易确定何时解除监听。此时可以使用弱监听器,把如下代码:
   15   ///   <code> 
  16   ///   observed.SomeEvent += observer.SomeEventHandler;
   17   ///   </code> 
  18   ///   改为:
   19   ///   <code> 
  20   ///   observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(
   21   ///       observer.SomeEventHandler, 
   22   ///       (handler, param) => observed.SomeEvent -= handler, 
   23   ///       null);
   24   ///   </code> 
  25   ///   上面的代码使用了lambda表达式以捕获变量,也可以像下面那样使用param参数:
   26   ///   <code> 
  27   ///   observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(
   28   ///       observer.SomeEventHandler, 
   29   ///       OnUnregisterWeakEvent, 
   30   ///       observed);
   31   ///  
  32   ///   void OnUnregisterWeakEvent(EventHandler&lt;E&gt; handler, object param)
   33   ///   {
   34   ///       ((ObservedType)param).SomeEvent -= handler;
   35   ///   }
   36   ///   </code> 
  37   ///   或者使用如下形式:
   38   ///   <code> 
  39   ///   observed.SomeEvent += WeakEventHandlerFactory.MakeWeak2(
   40   ///       observer.SomeEventHandler, observed, "SomeEvent");
   41   ///   </code> 
  42   ///   其中MakeWeak的第二个参数将弱监听器从事件源中移除。即使将第二个参数指定
   43   ///   为null,也不会阻止observer对象被垃圾收集,但事件源中将始终保持一个轻量
   44   ///   对象的引用。 
   45   ///   </summary> 
  46   public   static   class   WeakEventHandlerFactory
   47   {
   48       ///   <summary> 
  49       ///   我们在MakeWeak方法中使用反射创建WeakEventHandler的实例,所以在(1)
   50       ///   处理无法指定泛型参数T,以完成转换,此接口用于简化这一步骤。
   51       ///   </summary> 
  52       ///   <typeparam name="E">  事件参数类型。  </typeparam> 
  53       private   interface  IWeakEventHandler<E>  where   E : EventArgs
   54       {
   55           ///   <summary> 
  56           ///   事件处理器。
   57           ///   </summary> 
  58          EventHandler<E>  Handler
   59           {
   60               get  ;
   61           }
   62       }
   63  
  64       ///   <summary> 
  65       ///   对指定的事件处理函数创建一个弱监听器。
   66       ///   </summary> 
  67       ///   <typeparam name="E">  事件参数类型。  </typeparam> 
  68       ///   <param name="handler">  被包装的事件处理器。  </param> 
  69       ///   <param name="unregister">  用于将弱监听器从事件源中移除的委托。可以指
   70       ///   定为null,这时事件源中将始终保持一个轻量对象的引用,但不会阻止被包
   71       ///   装的对象被垃圾收集。  </param> 
  72       ///   <param name="param">  在调用unregister时使用的额外参数,可以是null。  </param> 
  73       ///   <returns>  生成的弱监听器。  </returns> 
  74       public   static  EventHandler<E> MakeWeak<E>(EventHandler<E>  handler,
   75          UnregisterCallback<E> unregister,  object  param)  where   E : EventArgs
   76       {
   77           if  (handler ==  null  )
   78           {
   79               throw   new  ArgumentNullException( "  handler  "  );
   80           }
   81  
  82           if  (handler.Method.IsStatic || handler.Target ==  null  )
   83           {
   84               throw   new  ArgumentException( "  Only instance methods are supported.  " ,  "  handler  "  );
   85           }
   86  
  87           var  type =  typeof (WeakEventHandler<,> ).MakeGenericType(
   88              handler.Method.DeclaringType,  typeof  (E));
   89  
  90           var  wehConstructor = type.GetConstructor(  new  []
   91           { 
   92               typeof (EventHandler<E> ), 
   93               typeof (UnregisterCallback<E> ),
   94               typeof ( object  )
   95           });
   96  
  97           //   (1) 
  98           var  weak = (IWeakEventHandler<E> )wehConstructor.Invoke(
   99               new   [] { handler, unregister, param });
  100           return   weak.Handler;
  101       }
  102  
 103       ///   <summary> 
 104       ///   此方法相当于MakeWeak(handler, unregister, null)。
  105       ///   </summary> 
 106       public   static  EventHandler<E> MakeWeak<E>(EventHandler<E>  handler,
  107          UnregisterCallback<E> unregister)  where   E : EventArgs
  108       {
  109           return  MakeWeak(handler, unregister, ( object ) null  );
  110       }
  111  
 112       ///   <summary> 
 113       ///   使用CreateUnregisterCallback创建取消弱监听器委托的形式注册监听器。
  114       ///   </summary> 
 115       ///   <typeparam name="E">  事件参数类型。  </typeparam> 
 116       ///   <param name="handler">  被包装的事件处理器。  </param> 
 117       ///   <param name="observed">  被监听的对象。  </param> 
 118       ///   <param name="eventName">  监听的事件名称。  </param> 
 119       ///   <returns>  生成的弱监听器。  </returns> 
 120       public   static  EventHandler<E> MakeWeak2<E>(EventHandler<E>  handler,
  121           object  observed,  string  eventName)  where   E : EventArgs
  122       {
  123           return  MakeWeak(handler, CreateUnregisterCallback<E> (observed, eventName));
  124       }
  125  
 126       ///   <summary> 
 127       ///   创建一个用于取消弱监听器注册的委托。
  128       ///   </summary> 
 129       ///   <typeparam name="E">  事件参数类型。  </typeparam> 
 130       ///   <param name="observed">  被监听的对象。  </param> 
 131       ///   <param name="eventName">  监听的事件名称。  </param> 
 132       ///   <returns>  创建结果,不会是null。  </returns> 
 133       public   static  UnregisterCallback<E> CreateUnregisterCallback<E> (
  134           object  observed,  string  eventName)  where   E : EventArgs
  135       {
  136           return   new  UnregisterHelper<E> (observed, eventName).Callback;
  137       }
  138  
 139       ///   <summary> 
 140       ///   用于将弱监听器从事件源中移除的辅助类,在C++/CLI等不支持lambda表示式
  141       ///   和自动委托的语言中,使用弱监听器的语法可能很复杂,此类用于简化这种
  142       ///   情况。
  143       ///   </summary> 
 144       ///   <typeparam name="E">  委托事件参数类型。  </typeparam> 
 145       private   class  UnregisterHelper<E>  where   E : EventArgs
  146       {
  147           ///   <summary> 
 148           ///   被监听的对象。
  149           ///   </summary> 
 150           private   readonly   object   observed;
  151  
 152           ///   <summary> 
 153           ///   事件名称。
  154           ///   </summary> 
 155           private   readonly   string   eventName;
  156  
 157           ///   <summary> 
 158           ///   构造函数。
  159           ///   </summary> 
 160           internal  UnregisterHelper( object  observed,  string   eventName)
  161           {
  162               this .observed =  observed;
  163               this .eventName =  eventName;
  164           }
  165  
 166           ///   <summary> 
 167           ///   用于取消监听的委托。
  168           ///   </summary> 
 169           internal  UnregisterCallback<E>  Callback
  170           {
  171               get 
 172               {
  173                   return  (handler, param) =>
 174                   {
  175                       var  info =  observed.GetType().GetEvent(eventName);
  176                       info.RemoveEventHandler(observed, handler);
  177                   };
  178               }
  179           }
  180       }
  181  
 182       ///   <summary> 
 183       ///   弱事件监听器。
  184       ///   </summary> 
 185       ///   <typeparam name="T">  监听者的类型。  </typeparam> 
 186       ///   <typeparam name="E">  事件参数类型。  </typeparam> 
 187       private   class  WeakEventHandler<T, E> : IWeakEventHandler<E>
 188           where  T :  class   where   E : EventArgs
  189       {
  190          ///   <summary> 
 191           ///   对监听器的弱引用。
  192           ///   </summary> 
 193           private   readonly   WeakReference weakReference;
  194  
 195           ///   <summary> 
 196           ///   用于调用被包装的监听器的委托。
  197           ///   </summary> 
 198           private   readonly   OpenEventHandler openHandler;
  199  
 200           ///   <summary> 
 201           ///   调用unregister时的额外参数。
  202           ///   </summary> 
 203           private   readonly   object   param;
  204  
 205           ///   <summary> 
 206           ///   监听器移除委托。
  207           ///   </summary> 
 208           private  UnregisterCallback<E>  unregister;
  209  
 210           ///   <summary> 
 211           ///   构造函数。
  212           ///   </summary> 
 213           ///   <param name="handler">  被包装的事件处理器。  </param> 
 214           ///   <param name="unregister">  用于移除弱监听器的代码。  </param> 
 215           ///   <param name="param">  调用unregister时的额外参数。  </param> 
 216           public  WeakEventHandler(EventHandler<E>  handler,
  217              UnregisterCallback<E> unregister,  object   param)
  218           {
  219              weakReference =  new   WeakReference(handler.Target);
  220              openHandler =  (OpenEventHandler)Delegate.CreateDelegate(
  221                   typeof (OpenEventHandler),  null  , handler.Method);
  222              Handler =  Invoke;
  223               this .unregister =  unregister;
  224               this .param =  param;
  225           }
  226  
 227           ///   <summary> 
 228           ///   包装监听器事件处理函数的开放委托类型。
  229           ///   </summary> 
 230           private   delegate   void  OpenEventHandler(T @this,  object   sender, E e);
  231  
 232           ///   <summary> 
 233           ///   <see>  IWeakEventHandler.Handler  </see> 
 234           ///   </summary> 
 235           public  EventHandler<E>  Handler
  236           {
  237               get  ;
  238               private   set  ;
  239           }
  240  
 241           ///   <summary> 
 242           ///   弱监听器事件处理函数。
  243           ///   </summary> 
 244           ///   <param name="sender">  引发事件的对象。  </param> 
 245           ///   <param name="e">  事件参数。  </param> 
 246           private   void  Invoke( object   sender, E e)
  247           {
  248              T target =  (T)weakReference.Target;
  249               if  (target !=  null  )
  250               {
  251                   openHandler.Invoke(target, sender, e);
  252               }
  253               else   if  (unregister !=  null  )
  254               {
  255                   unregister(Handler, param);
  256                  unregister =  null  ;
  257               }
  258           }
  259       }
  260  }

此工具的具体使用方法可以参考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。

 

.NET陷阱

 

.NET陷阱之四:事件监听带来的问题与弱监听器

摘要: 大家可能都遇到过没有取消事件监听而带来的一些问题,像内存泄露、访问无效数据等。当我们写下如下代码时:source.StateChanged += observer.SourceStateChangedHandler实际上source会保持有对observer的一个引用,所以如果source的生命期长于observer的话,则当其它地方不引用observer时,如果不显示解除监听,则observer不会被垃圾回收。这可能会带来两个问题:其一,如果observer占用了大量内存的话,则这部分内存不会被释放;其二,程序的其它地方可能已经处于不一致的状态,这样当source.StateChanged事 阅读全文

posted @  2013-04-08 18:43  Bruce Bi 阅读(21) |  评论 (0)   编辑

.NET陷阱之三:“正确”使用控件也会造成内存泄露

摘要: 在我们的代码中,有时会在控件中添加对数据对象的引用。比如使用树节点的Tag属性保存相应的对象,以便在界面操作中能简单的进行访问。因为其它地方不会引用这些数据,所以我们期望在控件被销毁时,垃圾回收机制能回收相应的内存。但当软件运行了一段时间后,内存使用量会变得非常大。下面是简化后的示例代码: 1 using System; 2 using System.Windows.Forms; 3 4 namespace MemoryLeak 5 { 6 public class MainForm : Form 7 { 8 private Button holderButt... 阅读全文

posted @  2013-04-03 11:21  Bruce Bi 阅读(45) |  评论 (1)   编辑

.NET陷阱之二:行为诡异的光标

摘要: 我们的软件中需要很多自定义的光标,以便在用户交互过程中进行必要的提示。我们开始的做法是将光标放到资源文件中,然后用类似下面的代码加载:var cursor = new Cursor(new MemoryStream(Resource.OpenHandIcon));... ...if (useDefaultCursor){ control.Cursor = Cursors.Default;}else{ control.Cursor = cursor;}但在测试过程中应该显示自定义光标时,总是时而替换成功,时而替换不成功。原来是.NET中提供的Cursor类的问题,Cursor的构造函... 阅读全文

posted @  2013-04-02 17:11  Bruce Bi 阅读(40) |  评论 (0)   编辑

.NET陷阱之一:IDeserializationCallback带来的问题

摘要: 代码中有一个类,其中包含一个字典(Dictionary<Key, Value>),本来想让前者实现IDeserializationCallback接口,以便在反序列化时根据字典的内容做一些初始化工作,结果循环字典元素的代码就是不走。费了好大劲才找到原因,先来看有问题的代码: 1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Runtime.Serialization; 5 using System.Runtime.Serialization.Formatters 阅读全文

posted @  2013-04-01 17:40  Bruce Bi 阅读(10) |  评论 (0)   编辑

 

分类:  .NET陷阱

标签:  .NET ,  Weak event listener

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于.NET陷阱之四:事件监听带来的问题与弱监听器的详细内容...

  阅读:36次