事件(Event),绝大多数内存泄漏(Memory Leak)的元凶[下篇] (
在上篇中我们谈到:将一个生命周期较短的对象(对象A)注册到一个生命周期较长(对象B)的某个事件(Event)上,两者便无形之间建立一个引用关系(B引用A)。这种引用关系导致GC在进行垃圾回收的时候不会将A是为垃圾对象,最终使其常驻内存(或者说将A捆绑到B上,具有了和B一样的生命周期)。这种让无用的对象不能被GC垃圾回收的现象,在托管环境下就是一种典型的内存泄漏问题。我们今天将会着重解释其背后的原因。[本篇文章的Source Code从这里下载) 一、CLR垃圾回收简介 在一个托管应用程序中,我们通过不同的方式创建一个托管对象(比如通过new关键字、反射或反序列化等)时,CLR会在托管堆为该对象开辟一块内存空间。对象的本质就是存储于某块内存中数据的体现,对象的生命周期终止于相应内存被回收之时。对于CLR来说,负责对托管堆(在这里主要指GC堆)进行回收的组件是垃圾收集器(GC),GC掌握着托管对象的生杀大权,决定着托管对象的生命周期。 当GC在进行垃圾回收的时候,会将“无用”的对象标记为垃圾对象,然后再对垃圾对象进行清理。GC对“无用”对象的识别机制很简单:判断对象是否被“根(Root)”所引用。在这里,“根”是对一组当前正被使用,或者以后可能被使用的对象的统称,大体包括这样的对象:类型的静态字段或当前的方法参数和局部变量、CPU寄存器等。 所以,孤立存在的对象将难逃被GC回收的厄运。反之,如果希望某个对象常驻内存中,我们唯一的方式就是通过某个“根”引用该对象。如果想让对象实例按照我们希望的方式创建、存活和消亡,所以我们唯一的方式也只能是:在希望它存活的时候让它被某个“根”引用,从而阻止GC将其回收;在希望它被回收的时候连“根”去除,使GC能够将其回收。 二、关于事件(Event)那点事 简单介绍了CLR的垃圾回收机制,我们再来谈谈关于事件的话题。我们知道,事件本质上就是一个System.Delegate对象。Delegate是一个特别的对象,我们单从语意上去来理解Delegate:Delegate的中文翻译是“代理”,意思是委托某人做某事。比如说,我请某人作为我们的代理律师打官司,就是一个很好的Delegate的例子。仔细分析我举的这个例子,我们可以将一个Delegate分解成两个部分:委托的事情(打官司)和委托的对象(某个律师)。与之相似地,.NET的Delegate对象同样可以分解成两个部分:委托的功能(Method)和目标对象(Target),这可以直接从Delegate的定义就可以看出来: 1:?
3: { 5: public MethodInfo Method { get; }
7: } 我们最常用的两个事件处理类型EventHandler和EventHandler<TEventArgs>本质上就是一个Delegate,下面是它们的定义。但是并不是直接继承自System.Delegate,而是继承自System.MulticastDelegate,MulticastDelegate派生于Delegate。MulticastDelegate不涉及到本篇文章的主题,在这里就不再赘言介绍了。 delegate void EventHandler(object sender,EventArgs e); 4: [Serializable] 1: private void TodoListForm_Load( 2: { 4: TodoListManager.Instance.TodoListChanged += TodoListManager_TodoListChanged; 6:? 8: { 10: state => 12: BindingSource bindingSource = new BindingSource();
14: this.dataGridViewTodoList.DataSource = bindingSource;
16: } ? 三、有什么方式能够更好的解决这个问题吗? 上面的这个问题可以简单地通过在某些时机解除事件的注册的方式来解决,所以很多人认为这是由不好的编程习惯造成的,不应该是一个问题。不错,作为一个优秀的编程人员,在编写事件注册的时候应该具一种意识:是否应该在某个时机解除该事件的注册。但是,再强的老虎也有打盹的时候,况且我们面对的开发人员也许没有你想的那么优秀。此外,作为一个架构师或者是框架的设计者,是否应该考虑提高你应用的容错能力呢?我的意思是:既然这是一个大家普遍会犯的毛病,那么你应该考虑提高你程序的健壮性以容忍开发人员犯这种“大众性的错误”。 我们具体的做法其实并不复杂,仅仅是写了如下一个特殊的WeakReferenceHandler对现有的EventHandler<TEventArgs>进行了改造,下面是实现WeakReferenceHandler的所有代码。我们通过传入EventHandler<TEventArgs>对象构造WeakReferenceHandler,在EventHandler<TEventArgs>的Target属性基础上建立WeakReference对象,在执行处理事件的时候通过该WeakReference找到真正的目标对象,如果找得到则通过反射在其基础上调用相应的方法;反之,如果通过不能得到Target,那么表明该事件的监听对象已经被GC当作垃圾对象回收掉了。为了在注册事件的时候方遍,特定义了一个隐式的类型转换:WeakReferenceHandler转换成EventHandler<TEventArgs>。 using System.Reflection;
4: { 6: { 8: { get; private set; }
10: public MethodInfo Method
12:? 14: { get; 15:?
17: { 19: Method = eventHandler.Method; 21: } 23: void Invoke( 24: { 26: if (null != target) 28: Method.Invoke(target,1)">new object[] { sender,e });
30: } 32: static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> weakHandler) 34: return weakHandler.Handler;
36: } 2: { 4: } 不过,任何事情都有其两面性,很难同时兼顾(比如软件架构下的Performance和Scalability),基于上面这种解决方式虽然能够有效地解决由于事件注册导致的内存泄露问题,但是会带来一定的性能损失,毕竟原来直接的事件注册方式是一种“强类型的Delegate”,具有更好的执行性能。关于本篇文章提供的实现方式,基本上借鉴了这篇文章:《[转]如何解决事件导致的Memory Leak问题:Weak Event Handlers》,有兴趣的朋友不妨认真读读。 作者:Artech
出处:http://artech.cnblogs.com/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- asp.net-mvc – 我们可以传递模型作为参数在RedirectToActi
- asp.net – 使用httpModule手动启用压缩
- asp.net-mvc – ASP.NET MVC使用相同的控制器分离移动视图
- asp.net – ASP.NET中的“关键字不支持:”错误
- 什么时候最早我可以在ASP.NET MVC页面生命周期中访问SESSIO
- asp.net-mvc – 在ASP.NET MVC中使用DotNetOpenId Remember
- asp.net – 实体框架CTP5,代码优先.可选的导航属性
- asp.net – 以编程方式刷新/更新HttpContext.User
- asp.net – System.Reflection.Assembly.LoadFile锁定文件
- asp.net – 如何以编程方式将SMTP服务器详细信息存储(保存)
- asp.net – 注册.NET 4.5 IIS 10 Windows 10
- asp.net – Windows Azure一般问题
- asp.net-mvc-3 – 在LINQ Query中调用一个方法
- MVC3&Razor – 将DateTime字符串从“mm / dd
- 如何获得干净的ASP.Net错误页面?
- asp.net-mvc – MVC Razor – 如何向自己提交表单
- 为什么我的ASP.NET页面将前缀’ctl00_ctl00’添加
- 如何防止ASP.NET站点的图像热链接?
- asp.net-mvc – 使用自定义格式的ASP.NET MVC Vi
- asp.net-mvc – 在ASP.NET MVC中封装用户控件