事件是我們?cè)赪PF開(kāi)發(fā)過(guò)程中使用的非常多的技術(shù),但是如果一不小心就會(huì)發(fā)生內(nèi)存泄漏葡粒,請(qǐng)看下面的Demo份殿。
我創(chuàng)建了一個(gè)簡(jiǎn)單的窗口:
<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)生一個(gè)大的內(nèi)存占用,約50MB嗽交,用于在任務(wù)管理器看到這個(gè)窗口Show出來(lái)以后卿嘲,進(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)閉,那么顯然這個(gè)window生命周期結(jié)束夫壁,再無(wú)引用拾枣。執(zhí)行:
GC.Collect();
window占用的50MB內(nèi)存應(yīng)該被回收,然后在任務(wù)管理器中看此進(jìn)程盒让,其內(nèi)存并沒(méi)有被回收梅肤。什么東西阻止了我回收?阻止回收的根本原因是仍然有人引用邑茄!我們來(lá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ā)生的時(shí)候可以通過(guò)Target調(diào)用Target中的Method,所以Application.Current.Exit += Current_Exit;
這句話就將window強(qiáng)引用了黔寇。而Application.Current
是靜態(tài)變量偶器,那么就會(huì)造成window
被Application.Current
引用,只有Application.Current
被回收才能回收window
了缝裤。
內(nèi)存泄漏原因深度剖析及解決措施
本質(zhì)的原因就是有更長(zhǎng)生命周期的對(duì)象持有對(duì)你的引用屏轰,造成無(wú)法回收。
上述問(wèn)題談到Application.Current.Exit += Current_Exit;
憋飞,這句話造成了對(duì)window
的強(qiáng)引用霎苗,所以你不用的時(shí)候手動(dòng)解除一下引用關(guān)系就可以回收了:Application.Current.Exit -= Current_Exit;
說(shuō)到事件處理函數(shù)不得不提匿名函數(shù),我們就本例再換種方式:Application.Current.Exit += (s, e) => { Console.WriteLine(DateTime.Now); };
這樣就不會(huì)阻止垃圾回收榛做,而Application.Current.Exit += (s, e) => { Console.WriteLine(this.Width); };
唁盏,window
就無(wú)法回收了。
匿名函數(shù)到底發(fā)生了什么检眯?為什么有時(shí)會(huì)阻止回收厘擂,有時(shí)不會(huì)?請(qǐng)看我的另一篇文章:《原來(lái)是這樣:C#中的匿名函數(shù) & 閉包(未完成)》
至此事件锰瘸,包括非靜態(tài)事件例如父對(duì)象的事件刽严,子對(duì)象訂閱了”苣或者父對(duì)象的事件處理器是子對(duì)象的:
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)
{
}
}
等等都會(huì)造成內(nèi)存泄漏舞萄。
我們知曉了造成內(nèi)存泄漏的根本原因就是還有引用,解決措施就很簡(jiǎn)單了:在不需要的時(shí)候解除引用恕曲。