1.Event 事件内存泄漏
中心思想一句话:当观察者注册在了一个生命周期长于自己的事件主题上,观察者不能被内存回收。
举例:写了一个小Demo来说明问题,Form1窗体用来验证输入的数字是否为质数(大于1只能被1或自身整除的数是质数),垃圾回收按钮是在调用GC清理内存,并显示当前能存中对象的数量;
定义事件参数类型NumEventArgs:
public class NumEventArgs:EventArgs { public NumEventArgs(int num) { Num = num; } public int Num{get;set;} }
定义了一个计算管理类:
public class CalculteManager { public event EventHandlerEventCalculate; public void RaiseEventCalculate(NumEventArgs arg) { if (EventCalculate != null) { EventCalculate(this, arg); } } }
这里面的发布者就是窗体1,订阅者Subscriber定义如下:
public class Subscriber { public Subscriber() { Interlocked.Increment(ref calcultor_count); } ~Subscriber() { Interlocked.Decrement(ref calcultor_count); } //记录内存中Subscriber的个数,分析内存泄漏情况 public static int calcultor_count; bool isPrimeNumber(int number) { if (number <= 0) throw new ArgumentException("A方法的输入必须大于0"); if (number == 1) throw new ArgumentException("1不是质数也不是合数"); for (int i = 2; i < number; i++) { if (number % i == 0) { return false; } } return true; } public void EventCallback(object sender, NumEventArgs e) { string b = isPrimeNumber(e.Num) == true ? "是" : "不是"; MessageBox.Show(string.Format("{0}{1}质数!",e.Num,b)); } }
好了,现在开始实现注册与发布事件了,质数验证器按钮触发的Clike事件下:
public partial class Form1 : Form { public Form1() { InitializeComponent(); } //质数验证按钮 private void button1_Click(object sender, EventArgs e) { int num; if (int.TryParse(this.textBox1.Text, out num)) { NumEventArgs arg = new NumEventArgs(num); var publishser = new CalculteManager(); publishser.EventCalculate += ((new Subscriber()).EventCallback); publishser.RaiseEventCalculate(arg); } } private void button2_Click(object sender, EventArgs e) { GC.Collect(); MessageBox.Show(Subscriber.calcultor_count.ToString()); } }
现在代码看起来一点问题没有,我们测试下,预计结果应该是:点击垃圾回收按钮时,内存中订阅者的数量为零才对
结果确实和我们想的一样,点击了2次质数验证器按钮,new了2次subscriber对象,但是publisher是局部变量,出click后就被销户了,所以subscriber对象也被回收了;
好,我们动一下代码,把publisher变成成员变量再试一下
public partial class Form1 : Form { public Form1() { InitializeComponent(); } CalculteManager publishser = new CalculteManager(); private void button1_Click(object sender, EventArgs e) { int num; if (int.TryParse(this.textBox1.Text, out num)) { NumEventArgs arg = new NumEventArgs(num); publishser.EventCalculate += ((new Subscriber()).EventCallback); publishser.RaiseEventCalculate(arg); } } private void button2_Click(object sender, EventArgs e) { GC.Collect(); MessageBox.Show(Subscriber.calcultor_count.ToString()); } }
这次猜想一下内存中的subscriber对象数量是否还为零,执行代码看一下:
点击了2次质数验证按钮,new了2个subscriber处理,然后点击垃圾回收按钮,发现内存中的2个subscriber没有被回收掉,原因很简单:当观察者注册在了一个生命周期长于自己的事件主题上,观察者不能被内存回收。
publishser 是成员变量一直存活,也就是publisher一直强引用着内存里面的2个subscriber对象,垃圾回收器只回收“无用”的对象,不会回收有“根引用”的对象,那么该怎么解决这个问题呢?
1.事假卸载 -=
public partial class Form1 : Form { public Form1() { InitializeComponent(); } CalculteManager publishser = new CalculteManager(); private void button1_Click(object sender, EventArgs e) { int num; if (int.TryParse(this.textBox1.Text, out num)) { NumEventArgs arg = new NumEventArgs(num); Subscriber subscriber = new Subscriber(); publishser.EventCalculate += subscriber.EventCallback; publishser.RaiseEventCalculate(arg); publishser.EventCalculate -= subscriber.EventCallback; } } private void button2_Click(object sender, EventArgs e) { GC.Collect(); MessageBox.Show(Subscriber.calcultor_count.ToString()); } }
事件卸载可以解决当下的问题,但是对于大多数程序员来说都会有头脑发热的情况(忘记卸载事件),那么有没有一个完美的解决办法呢,有!用弱引用,啥叫弱引用呢?
就是说A引用着B的同时,B依然可以被垃圾回收器回收掉
2.说干就干,定义一个事件注册适配器,用于注册事件的
public class WeakEventHandlerwhere TEventArgs : EventArgs {
public WeakReference Reference { get; private set; } public MethodInfo Method { get; private set; } public EventHandlerHandler { get; private set; } public WeakEventHandler(EventHandler eventHandler) { Reference = new WeakReference(eventHandler.Target); Method = eventHandler.Method; Handler = Invoke; } public void Invoke(object sender, TEventArgs e) { object target = Reference.Target; if (null != target) { Method.Invoke(target, new object[] { sender, e }); } } public static implicit operator EventHandler (WeakEventHandler weakHandler) { return weakHandler.Handler; } }
我们知道,事件本质上就是一个System.Delegate对象。Delegate是一个特别的对象,我们单从语意上去来理解Delegate:Delegate的中文翻译是“代理”,意思是委托某人做某事。比如说,我请某人作为我们的代理律师打官司,就是一个很好的Delegate的例子。仔细分析我举的这个例子,我们可以将一个Delegate分解成两个部分:
委托的事情(打官司)和委托的对象(某个律师)。与之相似地,.NET的Delegate对象同样可以分解成两个部分:委托的功能(Method)和目标对象(Target),这可以直接从Delegate的定义就可以看出来:
1: 2: public abstract class Delegate : ICloneable, ISerializable 3: { 4: // Others 5: public MethodInfo Method { get; } 6: public object Target { get; } 7: }
我们最常用的两个事件处理类型EventHandler和EventHandler
1: [Serializable, ComVisible(true)] 2: public delegate void EventHandler(object sender, EventArgs e); 3: 4: [Serializable] 5: public delegate void EventHandler(object sender, TEventArgs e) where TEventArgs: EventArgs;
public static implicit operator EventHandler(WeakEventHandler WeakEventHandlerweakHandler)定义了 到EventHandler 的隐式转换;
好了,用一下我们的这个弱引用适配器吧,修改代码:
public partial class Form1 : Form { public Form1() { InitializeComponent(); } CalculteManager publishser = new CalculteManager(); private void button1_Click(object sender, EventArgs e) { int num; if (int.TryParse(this.textBox1.Text, out num)) { NumEventArgs arg = new NumEventArgs(num); Subscriber subscriber = new Subscriber(); publishser.EventCalculate += new WeakEventHandler(subscriber.EventCallback); publishser.RaiseEventCalculate(arg); } } private void button2_Click(object sender, EventArgs e) { GC.Collect(); MessageBox.Show(Subscriber.calcultor_count.ToString()); } }
测试下:注意我们每次点击回收按钮要点击2次才能回收完成哦
完美!这就是弱引用的功劳,怎么样把弱引用适配器的类放到项目中,再也不担心小白的们事件内存泄漏了,好用把!
但是
这个弱引用的适配器类有个性能问题,不知道你有没有发现,“反射”效率会比直接调用效率低很多,怎么办呢?我们来优化掉这个反射低效率部分,直接给出代码:
public class WeakEventHandlerwhere TEventArgs : EventArgs { public WeakReference Reference { get; private set; } public MethodInfo Method { get; private set; } public EventHandler Handler { get; private set; } public WeakEventHandler(EventHandler eventHandler) { Reference = new WeakReference(eventHandler.Target); Method = eventHandler.Method; Handler = Invoke; } public void Invoke(object sender, TEventArgs e) { object target = Reference.Target; if (null != target) {
//反射慢的原因就是弱类型的参数验证,改成强类型就会快好多了,用委托优化比直接调用只慢了一点点,就是创建委托的那部分而已,速度上几乎无差别
//Method.Invoke(target, new object[] { sender, e });
//优化反射
EventHandlercallBack = (EventHandler ), target, Method);)Delegate.CreateDelegate(typeof(EventHandler
callBack.Invoke(target,e );
} }
public static implicit operator EventHandler(WeakEventHandler weakHandler) { return weakHandler.Handler; } }
写了这么多,兄台你不顶一下吗?
源码下载
参考以下大佬们文章: