事件是我們在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)變量,那么就會造成window被Application.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)存泄漏的根本原因就是還有引用,解決措施就很簡單了:在不需要的時候解除引用。