.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<E> 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://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于.NET陷阱之四:事件监听带来的问题与弱监听器的详细内容...