鄭州網(wǎng)站制作怎么樣江蘇seo平臺(tái)
背景
在開發(fā)中由于對(duì)語(yǔ)言特性不了解或經(jīng)驗(yàn)不足或疏忽,往往會(huì)造成一些低級(jí)bug。而內(nèi)存泄漏就是最常見的一個(gè),這個(gè)問(wèn)題在測(cè)試過(guò)程中,因?yàn)椴僮黝l次低,而不能完全被暴露出來(lái);而在正式使用時(shí),由于使用次數(shù)增加,這個(gè)問(wèn)題在很快就會(huì)出現(xiàn)。一旦出現(xiàn)就會(huì)導(dǎo)致程序直接退出或報(bào)錯(cuò)……使用中得益于使用量的增加,未被回收的小對(duì)象不斷實(shí)例化,數(shù)量的疊加,導(dǎo)致內(nèi)存使用率會(huì)隨時(shí)間的增長(zhǎng)而增加,直到影響程序的正常執(zhí)行。
為了警醒鄙人,同時(shí)方便以后查閱,將在項(xiàng)目中實(shí)際處理的內(nèi)存泄漏情況與處理辦法進(jìn)行下述總結(jié)。
常見泄漏
在C#中常見的內(nèi)存泄漏主要是由于事件訂閱造成:
- 實(shí)例類訂閱靜態(tài)類事件,不使用當(dāng)前實(shí)例時(shí)未取消訂閱,導(dǎo)致靜態(tài)類中一直持有訂閱方實(shí)例類,實(shí)例類不能釋放,而每次使用時(shí)不斷實(shí)例化后實(shí)例數(shù)量不斷增加。
- 實(shí)例類中有類似timer之類定時(shí)運(yùn)行的對(duì)象未釋放(未dispose),導(dǎo)致實(shí)例類不能回收,而實(shí)例類仍不斷實(shí)例化。
- 實(shí)例類中訂閱了另一個(gè)實(shí)例類中的事件,但另一個(gè)實(shí)例類的生命周期很長(zhǎng)(如果生命周期短,訂閱方在使用完后,若被訂閱方之后也完成了使命,理論上是可以很快被GC回收的),同時(shí)訂閱方在未使用時(shí)也未及時(shí)取消訂閱,導(dǎo)致被訂閱方長(zhǎng)時(shí)間持有訂閱實(shí)例。
- 其它訂閱未取消的情況。
實(shí)例類訂閱靜態(tài)類事件,但未取消訂閱
若在構(gòu)造函數(shù)中訂閱靜態(tài)類FolderSelect中的AllFolderg事件:
FolderSelect folderSelect = FolderSelect.Instance;
folderSelect.AllFolder += FolderSelect_AllFolder;
如果在不使用時(shí),不執(zhí)行?folderSelect.ScanAllFolder -= FolderSelect_ScanAllFolder;就會(huì)導(dǎo)致FolderSelect靜態(tài)類一直持有訂閱類,導(dǎo)致訂閱類不能被回收。
同時(shí)由于實(shí)例類,在實(shí)例化時(shí)會(huì)運(yùn)行構(gòu)造函數(shù),生成新的實(shí)例時(shí)會(huì)再次將新實(shí)例又再次訂閱這個(gè)事件。那么當(dāng)FolderSelect觸發(fā)AllFolder事件時(shí),新、舊實(shí)例都會(huì)執(zhí)行FolderSelect_AllFolderg事件,這也可能導(dǎo)致一些不必要的問(wèn)題。
實(shí)例類中timer未釋放
在某些情況下,在對(duì)象類中會(huì)使用timer,而timer在不使用時(shí),一定要dispose掉。由于timer在執(zhí)行定時(shí)事件時(shí)會(huì)一直持有當(dāng)前的對(duì)象,從而導(dǎo)致對(duì)象不能被回收。另.net中涉及到的timer有6種,詳細(xì)見Timer Class (System.Threading) | Microsoft Learn中的詳細(xì)介紹。
實(shí)例類訂閱長(zhǎng)生命周期實(shí)例類
以WINUI中常用的異常捕捉為例,若在Page的構(gòu)造函數(shù)中添加了下述代碼:
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
即每次實(shí)例化這個(gè)Page時(shí)都會(huì)訂閱CurrentDomain_FirstChanceException這個(gè)方法,而AppDomain的生命周期與程序一致,導(dǎo)致它會(huì)一直持有當(dāng)前訂閱方的實(shí)例,從而訂閱方不能被回收。
其他訂閱未取消
WINUI ComboBoxItem事件未取消
WINUI中的對(duì)ComboBox中的ComboBoxItem單獨(dú)添加了Tapped事件,而這個(gè)Tapped事件若在不使用時(shí)未取消訂閱,也會(huì)引起當(dāng)前使用的對(duì)象不能被回收。
在WINUI中的ComboBox的UI代碼如下:
<ComboBoxx:Name="ComboOrder"Width="268"Height="70"Margin="5,0,5,0"VerticalAlignment="Center"BorderThickness="0"FontSize="38"Foreground="White"Loaded="ComboOrder_Loaded"SelectedIndex="0"Style="{StaticResource DefaultComboBoxStyle2}"Tag="180"ToolTipService.ToolTip="排序"><ComboBoxItemx:Name="CbiPatientName"Content="患者名"Style="{StaticResource ComboBoxItemRevealStyle2}"Tag="0"Tapped="CbiName_Tapped" /><ComboBoxItemx:Name="CbiImportTime"Content="時(shí)間"Style="{StaticResource ComboBoxItemRevealStyle2}"Tag="0"Tapped="CbiImportTime_Tapped" /><ComboBoxItemx:Name="CbiPlanPhase"Content="階段"Style="{StaticResource ComboBoxItemRevealStyle2}"Tag="0"Tapped="CbiPlanPhase_Tapped" /></ComboBox>
在上述代碼中,為ComboBoxItem添加了Tapped事件,正是這個(gè)事件導(dǎo)致程序在退出ComboBox所在頁(yè)時(shí),它所在的Page不能及時(shí)被回收,導(dǎo)致再次進(jìn)入時(shí)會(huì)新增它所在的Page實(shí)例。為了避免此問(wèn)題,不得以重寫離開頁(yè)面方法?protected override void OnNavigatingFrom(NavigatingCancelEventArgs e),在這個(gè)方法中將ComboBoxItem的綁定事件全部取消。
可能原因:ComboBoxItem為ComboBox的子控件,導(dǎo)致ComboBoxItem的tapped事件的引用可能形成了閉包,導(dǎo)致它所在的Page不能被回收。后續(xù)搞清楚原因再做相應(yīng)更新。
取消訂閱
取消訂閱——對(duì)于事件訂閱造成的內(nèi)存泄漏,當(dāng)然是在不使用當(dāng)前對(duì)象時(shí),就及時(shí)將它訂閱的事件取消訂閱即可。詳細(xì)可參考如何訂閱和取消訂閱事件 - C# 編程指南 - C# | Microsoft Learn最下方的取消訂閱欄。
弱事件管理——另外事件也可以使用弱引用進(jìn)行相應(yīng)的操作,詳細(xì)見MSDNWeakEventManager 類 (System.Windows) | Microsoft Learn。
利用診斷預(yù)防內(nèi)存泄漏
除了在編程時(shí)就養(yǎng)成使用完訂閱事件就馬上取消,另外在進(jìn)行測(cè)試時(shí)也可以通過(guò)VisualStudio提供的診斷工具進(jìn)行診斷。使用方法如下,詳細(xì)參見MSDN。
診斷工具下方,選擇內(nèi)存使用率,然后在內(nèi)存使用率的面板左上角點(diǎn)擊截取快照,截取完成后如下,再點(diǎn)擊對(duì)象(差異)即可查看對(duì)象數(shù)量的情況。
在點(diǎn)擊上圖中的紅圈后,如下圖,在下圖中左上角類型面板中搜索查看的對(duì)象。另還可在下圖右上角與基線進(jìn)行比較中選擇一個(gè)你要比較的前一個(gè)內(nèi)存快照。