.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。

posted @ 2013-04-08 18:43  Bruce Bi  阅读(2462)  评论(5编辑  收藏  举报