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 EventHandler EventCalculate;
        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 WeakEventHandler where 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 }); } } 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本质上就是一个Delegate,下面是它们的定义。但是并不是直接继承自System.Delegate,而是继承自System.MulticastDelegate,MulticastDelegate派生于Delegate。MulticastDelegate不涉及到本篇文章的主题,在这里就不再赘言介绍了。

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 weakHandler)定义了WeakEventHandler 到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 WeakEventHandler where 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 });
                 //优化反射 

EventHandler callBack = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, Method);
callBack.Invoke(target,e );

} }

public static implicit operator EventHandler(WeakEventHandler weakHandler) { return weakHandler.Handler; } }

 写了这么多,兄台你不顶一下吗?

 



源码下载

 参考以下大佬们文章: