內(nèi)存泄漏之Event Handler

事件是我們在WPF開發(fā)過程中使用的非常多的技術(shù),但是如果一不小心就會發(fā)生內(nèi)存泄漏,請看下面的Demo。
我創(chuàng)建了一個簡單的窗口:

<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
{
    //這里產(chǎn)生一個大的內(nèi)存占用,約50MB,用于在任務(wù)管理器看到這個窗口Show出來以后,進(jìn)程內(nèi)存占用劇增的現(xiàn)象
    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);
    }
}

內(nèi)存泄漏現(xiàn)象

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

然后將此window直接關(guān)閉,那么顯然這個window生命周期結(jié)束,再無引用。執(zhí)行:

GC.Collect();

window占用的50MB內(nèi)存應(yīng)該被回收,然后在任務(wù)管理器中看此進(jìn)程,其內(nèi)存并沒有被回收。什么東西阻止了我回收?阻止回收的根本原因是仍然有人引用!我們來挖一下,深層次的原因在哪里。

源碼分析

window生命周期分析

最關(guān)鍵的代碼在此處:Application.Current.Exit += Current_Exit;這句話表示,我訂閱了Application的Exit事件,訂閱的完整代碼應(yīng)該是這樣:Application.Current.Exit += this.Current_Exit;,我們知道事件的工作原理是觀察者模式,要實(shí)現(xiàn)觀察者模式就必須有目標(biāo)Target和處理函數(shù)Method,這句話將this作為觀察者模式中的Target,Current_Exit作為Method,當(dāng)事件發(fā)生的時候可以通過Target調(diào)用Target中的Method,所以Application.Current.Exit += Current_Exit;這句話就將window強(qiáng)引用了。而Application.Current是靜態(tài)變量,那么就會造成windowApplication.Current引用,只有Application.Current被回收才能回收window了。

內(nèi)存泄漏原因深度剖析及解決措施

本質(zhì)的原因就是有更長生命周期的對象持有對你的引用,造成無法回收。
上述問題談到Application.Current.Exit += Current_Exit;,這句話造成了對window的強(qiáng)引用,所以你不用的時候手動解除一下引用關(guān)系就可以回收了:Application.Current.Exit -= Current_Exit;
說到事件處理函數(shù)不得不提匿名函數(shù),我們就本例再換種方式:Application.Current.Exit += (s, e) => { Console.WriteLine(DateTime.Now); };這樣就不會阻止垃圾回收,而Application.Current.Exit += (s, e) => { Console.WriteLine(this.Width); };,window就無法回收了。
匿名函數(shù)到底發(fā)生了什么?為什么有時會阻止回收,有時不會?請看我的另一篇文章:《原來是這樣:C#中的匿名函數(shù) & 閉包(未完成)》

至此事件,包括非靜態(tài)事件例如父對象的事件,子對象訂閱了。或者父對象的事件處理器是子對象的:

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)
    {
    }
}

等等都會造成內(nèi)存泄漏。
我們知曉了造成內(nèi)存泄漏的根本原因就是還有引用,解決措施就很簡單了:在不需要的時候解除引用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 靜態(tài)類里面的事件,再普通不過,經(jīng)常使用它來做全局廣播。但是如果一不小心就會發(fā)生內(nèi)存泄漏,請看下面的Demo:我創(chuàng)建...
    古意昌閱讀 597評論 0 0
  • 我們經(jīng)常會在程序中使用DispatcherTimer,但是如果一不小心就會發(fā)生內(nèi)存泄漏,請看下面的Demo: 內(nèi)存...
    古意昌閱讀 3,532評論 1 2
  • 其實(shí)想想,二十歲的自己真的很傻很天真,雖然已經(jīng)是兩個孩子的媽,可還是選擇了原諒,選擇了婚姻??墒牵覀兊降资菑氖裁?..
    陳珊_6624閱讀 194評論 0 0
  • 不知道從什么時候開始,我開始找各種理由逃避著現(xiàn)實(shí),高考失利的我并沒有選擇復(fù)讀,因?yàn)橛X得那是在浪費(fèi)爸媽的資源,我到了...
    劉稔稔稔閱讀 329評論 0 0
  • R: 在生氣時,批評和指責(zé)他人都無法真正傳達(dá)我們的心聲。如果想充分表達(dá)憤怒,我們就不能歸咎于他人,而把注意力放在...
    意為安閱讀 540評論 0 1

友情鏈接更多精彩內(nèi)容