共用方式為


避免記憶體流失

在受控的 XAML 應用程式中使用 Win2D 控件時,必須小心避免參考計數循環,防止垃圾收集器無法回收這些控件。

如果你有問題的話...

如果符合所有這些條件,參考計數週期會讓 Win2D 控件無法進行垃圾收集。 每次應用程式移至不同的頁面時,都會配置新的 Win2D 資源,但舊資源永遠不會釋放,因此記憶體會流失。 若要避免這種情況,您必須新增程式代碼來明確中斷迴圈。

修正方式

若要中斷參考計數週期,並讓您的頁面可被垃圾回收:

  • 掛鉤包含 Win2D 控制項的 XAML 頁面 Unloaded 事件
  • Unloaded 處理程式中,呼叫 Win2D 控制件上的 RemoveFromVisualTree
  • Unloaded 處理程式中,釋放 Win2D 控件的任何明確參照並將其設定為 null

範例程式代碼:

void page_Unloaded(object sender, RoutedEventArgs e)
{
    this.canvas.RemoveFromVisualTree();
    this.canvas = null;
}

如需正常運作的範例,請參閱任何 範例資源庫 示範頁面。

如何測試循環滲漏

若要測試您的應用程式是否正確地中斷 refcount 循環,請將完成項方法新增至包含 Win2D 控制件的任何 XAML 頁面:

~MyPage()
{
    System.Diagnostics.Debug.WriteLine("~" + GetType().Name);
}

App在您的建構函式中,設定定時器,以確保垃圾收集會定期進行:

var gcTimer = new DispatcherTimer();
gcTimer.Tick += (sender, e) => { GC.Collect(); };
gcTimer.Interval = TimeSpan.FromSeconds(1);
gcTimer.Start();

導航到該頁面,然後轉至其他頁面。 如果所有迴圈都中斷,您將會在一到兩秒內在 Visual Studio 的輸出窗格中看到 Debug.WriteLine 輸出。

請注意,呼叫 GC.Collect 會造成干擾並損害效能,因此您應該在完成洩漏測試后立即移除此測試程序代碼!

血腥的細節

當物件 A 具有 B 的參考,同時 B 也有 A 的參考時,就會發生迴圈。或者,當 A 參考 B 和 B 參考 C 時,而 C 則參考 A 等。

訂閱 XAML 控制件的事件時,這種循環幾乎不可避免:

  • XAML 頁面會保存其中的所有控制項的參考
  • 控件會保存已訂閱其事件的處理程式委派參考
  • 每個代表都會持有其目標實例的參考
  • 事件處理程式通常是 XAML 頁面類別的實例方法,因此其目標實例參考會指向 XAML 頁面,建立迴圈

如果所有涉及的物件都是在 .NET 中實作的,這類迴圈不會成為問題,因為 .NET 具備垃圾收集功能,垃圾收集演算法能夠識別並回收即使是在循環中連結的物件群組。

不同於 .NET,C++藉由參考計數來管理記憶體,而無法偵測和回收對象的迴圈。 儘管有這項限制,但使用 Win2D 的 C++ 應用程式沒有任何問題,因為 C++ 事件處理常式預設會保留弱式而非強式參考來指向其目標實例。 因此,頁面會參考控件,而控件會參考事件處理程式委派,但此委派不會參考回頁面,因此沒有迴圈。

當 .NET 應用程式使用C++ WinRT 元件,例如 Win2D 時,就會發生此問題:

  • XAML 頁面是應用程式的一部分,因此會使用資源回收
  • Win2D 控制件會在 C++中實作,因此會使用參考計數
  • 事件處理委託是應用程式的一部分,因此會使用垃圾收集,並對其目標實例保留強引用。

迴圈存在,但參與此迴圈的 Win2D 物件不會使用 .NET 垃圾收集。 這表示垃圾回收器無法檢視整個鏈,因此無法偵測或回收這些物件。 發生此情況時,應用程式必須通過明確地打破循環來協助解決。 這可以藉由釋放頁面中到控件的所有參考(如上所述的建議),或釋放控件中指向頁面的事件處理程式委派的所有參考來完成(使用頁面卸載事件取消訂閱所有事件處理程式)。