美文网首页程序员
内存泄漏之Event Handler

内存泄漏之Event Handler

作者: 古意昌 | 来源:发表于2018-01-17 19:38 被阅读0次

    事件是我们在WPF开发过程中使用的非常多的技术,但是如果一不小心就会发生内存泄漏,请看下面的Demo。
    我创建了一个简单的窗口:

    <Window x:Class="MemoryLeak.Example.Example3"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    </Window>
    

    Example3.xaml.cs中的代码如下:

    public partial class Example3
    {
        //这里产生一个大的内存占用,约50MB,用于在任务管理器看到这个窗口Show出来以后,进程内存占用剧增的现象
        private readonly List<string> _bigList = ExampleHelper.BigList();
    
        public Example3()
        {
            InitializeComponent();
    
            Application.Current.Exit += Current_Exit;
        }
    
        private void Current_Exit(object sender, ExitEventArgs e)
        {
            Console.WriteLine(DateTime.Now);
        }
    }
    

    内存泄漏现象

    Example3 window = new Example3();
    window.Show();
    

    然后将此window直接关闭,那么显然这个window生命周期结束,再无引用。执行:

    GC.Collect();
    

    window占用的50MB内存应该被回收,然后在任务管理器中看此进程,其内存并没有被回收。什么东西阻止了我回收?阻止回收的根本原因是仍然有人引用!我们来挖一下,深层次的原因在哪里。

    源码分析

    window生命周期分析

    最关键的代码在此处:Application.Current.Exit += Current_Exit;这句话表示,我订阅了Application的Exit事件,订阅的完整代码应该是这样:Application.Current.Exit += this.Current_Exit;,我们知道事件的工作原理是观察者模式,要实现观察者模式就必须有目标Target和处理函数Method,这句话将this作为观察者模式中的Target,Current_Exit作为Method,当事件发生的时候可以通过Target调用Target中的Method,所以Application.Current.Exit += Current_Exit;这句话就将window强引用了。而Application.Current是静态变量,那么就会造成windowApplication.Current引用,只有Application.Current被回收才能回收window了。

    内存泄漏原因深度剖析及解决措施

    本质的原因就是有更长生命周期的对象持有对你的引用,造成无法回收。
    上述问题谈到Application.Current.Exit += Current_Exit;,这句话造成了对window的强引用,所以你不用的时候手动解除一下引用关系就可以回收了:Application.Current.Exit -= Current_Exit;
    说到事件处理函数不得不提匿名函数,我们就本例再换种方式:Application.Current.Exit += (s, e) => { Console.WriteLine(DateTime.Now); };这样就不会阻止垃圾回收,而Application.Current.Exit += (s, e) => { Console.WriteLine(this.Width); };window就无法回收了。
    匿名函数到底发生了什么?为什么有时会阻止回收,有时不会?请看我的另一篇文章:《原来是这样:C#中的匿名函数 & 闭包(未完成)》

    至此事件,包括非静态事件例如父对象的事件,子对象订阅了。或者父对象的事件处理器是子对象的:

    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
    
            Example3 window = new Example3();
            ExampleTextBox.TextChanged += window.TextBox_TextChanged;
            window.Show();
        }
    }
    public partial class Example3
    {
        public Example3()
        {
            InitializeComponent();
        }
    
        public void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
        }
    }
    

    等等都会造成内存泄漏。
    我们知晓了造成内存泄漏的根本原因就是还有引用,解决措施就很简单了:在不需要的时候解除引用。

    相关文章

      网友评论

        本文标题:内存泄漏之Event Handler

        本文链接:https://www.haomeiwen.com/subject/hnnuoxtx.html