避免內存泄漏 |
本文從微軟官方文檔翻譯
http://microsoft.github.io/Win2D/html/RefCycles.htm
?如果文檔有問題,可以在 https://github.com/Nukepayload2/Win2dDocVB發 Issue,也可以直接回復。
當在托管的 XAML 應用程序中使用 Win2D 控件,需要注意垃圾回收器回收這些控件前它們的引用計數循環。
你有一個問題,如果...
您正在使用 Win2D 從一種.NET 語言如 VB (不是 c + +)
您使用 Win2D XAML 控件之一:
l? CanvasControl
l? CanvasVirtualControl
l? CanvasAnimatedControl
l? CanvasSwapChainPanel
l? 你訂閱 Win2D 控件的事件 (例如 繪制,CreateResources,SizeChanged...)
l? 您的應用程序多個 XAML 頁之間來回移動
如果滿足所有這些條件,引用計數循環將阻止 Win2D 控件被垃圾回收。新的 Win2D 資源分配每次應用程序移動到一個不同的頁,但舊的永遠不會被釋放,所以內存泄漏。要避免此問題,必須添加代碼來顯式地打破這種循環。
如何修復它
打破引用計數循環,讓你的頁面進行垃圾回收:
處理Xaml頁面或對話框的Unloaded事件
在卸載處理程序,調用 RemoveFromVisualTree Win2D 控件并釋放 (通過設置為 Nothing) 對 Win2D 控件的任何顯式引用
示例代碼:
VB
Private Sub page_Unloaded(sender As Object, e As RoutedEventArgs) Handles Me.UnloadedMe.canvas.RemoveFromVisualTreeMe.canvas = NothingEnd Sub
?
如何測試內存是否泄漏
若要測試是否您的應用程序正確打破引用循環,將代碼添加到包含 Win2D 控件的任何 XAML 頁或對話框的終結器方法:
VB
Protected Overrides Sub Finalize()Debug.WriteLine("回收畫布")MyBase.Finalize()End Sub
?
在您的應用程序的構造函數建立一個計時器,它將使確定垃圾收集發生在固定的時間間隔:
VB
Dim gcTimer As New DispatcherTimerAddHandler gcTimer.Tick, Sub() GC.CollectgcTimer.Interval = TimeSpan.FromSeconds(1)gcTimer.Start
?
導航到頁面,然后從它到其他頁面上。
引用循環打破后大概一秒你會在輸出窗口看到 "回收畫布"
請注意,調用 GC.Collect 會影響性能,所以您應該在測試后刪除此測試代碼
殘酷的細節
對象 A 引用了 B,同時 B 也引用 A.
這時發生一個循環。或者當 B 和 B 的引用引用 C,而 C 引用 A 等。
當訂閱事件的 XAML 控件,這種循環是幾乎不可避免:
l? XAML 頁保留對它所包含的所有控件的引用
l? 控件保持對已訂閱它們的事件處理程序委托的引用
l? 每個委托保存到其目標實例的引用
l? 事件處理程序通常是實例方法的 XAML 頁類,所以他們目標實例引用點返回到 XAML 頁面,創建一個循環
如果在.NET 中實現所有涉及的對象,這種循環不是問題因為.NET 垃圾回收,垃圾回收算法能夠識別并回收的對象組,即使它們鏈接在一個循環中。與.NET不同的是 c + + 管理內存的引用計數,無法檢測和回收循環對象。盡管有這種限制,使用 Win2D 的 c + + 應用程序沒有任何問題,因為 c + + 事件處理程序默認為弱引用而不是他們的目標實例的強引用。因此頁面引用該控件,而控件引用的事件處理程序委托,此委托未引用返回到頁面,所以沒有任何這種問題。
問題在于當.NET 應用程序使用 c + + WinRT 組件如 Win2D:
l? XAML 頁是應用程序的一部分,所以使用垃圾回收
l? Win2D 控制在 c + + 中實現,因此,使用引用計數
l? 事件處理程序委托是應用程序的一部分,所以使用垃圾回收,認為對其目標實例的強引用
一個引用循環是存在的但參加這個的 Win2D 對象不使用.NET 垃圾回收。這意味著垃圾收集器是無法看到整個鏈,因此它不能檢測或回收的對象。當這發生時,應用程序必須通過顯式打破循環幫忙。這可以通過釋放所有引用從頁面到控件 (如上文所建議) 或通過都釋放從控制到可能指向頁面 (使用頁卸載事件取消訂閱所有事件處理程序) 的事件處理程序委托的所有引用。