Menghindari kebocoran memori

Saat menggunakan kontrol Win2D dalam aplikasi XAML terkelola, hati-hati perlu dilakukan untuk menghindari siklus penghitung referensi yang mungkin mencegah kontrol ini untuk pernah direklamasi oleh pengumpul sampah.

Anda memiliki masalah jika...

Jika semua kondisi ini terpenuhi, siklus jumlah referensi akan menjaga kontrol Win2D agar tidak pernah menjadi sampah yang dikumpulkan. Sumber daya Win2D baru dialokasikan setiap kali aplikasi berpindah ke halaman yang berbeda, tetapi yang lama tidak pernah dibeberkan sehingga memori bocor. Untuk menghindari hal ini, Anda harus menambahkan kode untuk secara eksplisit memutus siklus.

Cara memperbaikinya

Untuk memutus siklus jumlah referensi dan membiarkan halaman Anda mengalami pengumpulan sampah:

  • Kaitkan peristiwa Unloaded halaman XAML yang berisi kontrol Win2D
  • Panggilkan Unloaded pada kontrol Win2D di handler RemoveFromVisualTree
  • Dalam handler Unloaded, lepaskan referensi eksplisit apa pun ke kontrol Win2D dengan mengatur ke null

Contoh kode:

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

Untuk contoh yang berfungsi, lihat salah satu halaman demo Galeri Contoh.

Cara menguji kebocoran siklus

Untuk menguji apakah aplikasi Anda memutuskan siklus penghitungan referensi dengan benar, tambahkan metode finalizer ke halaman XAML mana pun yang berisi kontrol Win2D:

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

Di konstruktor Anda App , siapkan timer yang akan memastikan pengumpulan sampah terjadi secara berkala:

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

Buka halaman tersebut, lalu pindah ke halaman lain. Jika semua siklus telah rusak, Anda akan melihat Debug.WriteLine output di panel output Visual Studio dalam satu atau dua detik.

Perhatikan bahwa panggilan GC.Collect mengganggu dan melukai performa, jadi Anda harus menghapus kode pengujian ini segera setelah Anda menyelesaikan pengujian untuk kebocoran!

Detail yang mengerikan

Siklus terjadi ketika objek A memiliki referensi ke B, pada saat yang sama dengan B juga memiliki referensi ke A. Atau saat A mereferensikan B, dan B mereferensikan C, sementara C mereferensikan A, dll.

Saat berlangganan peristiwa kontrol XAML, siklus semacam ini hampir tidak dapat dihindari.

  • Halaman XAML menyimpan referensi ke semua kontrol yang terkandung di dalamnya
  • Kontrol menyimpan referensi ke delegasi handler yang telah berlangganan acara mereka
  • Setiap delegasi menyimpan referensi ke instans targetnya
  • Penangan kejadian biasanya merupakan metode instans dari kelas halaman XAML, sehingga referensi instans target mereka menunjuk kembali ke halaman XAML, menciptakan siklus.

Jika semua objek yang terlibat diimplementasikan dalam .NET, siklus tersebut tidak menjadi masalah karena .NET adalah sampah yang dikumpulkan, dan algoritma pengumpulan sampah dapat mengidentifikasi dan mengklaim kembali kelompok objek bahkan jika ditautkan dalam siklus.

Tidak seperti .NET, C++ mengelola memori dengan penghitungan referensi, yang tidak dapat mendeteksi dan mengklaim kembali siklus objek. Terlepas dari batasan ini, aplikasi C++ yang menggunakan Win2D tidak memiliki masalah karena penanganan aktivitas C++ default untuk menyimpan referensi yang lemah daripada referensi yang kuat ke instans target mereka. Oleh karena itu halaman mereferensikan kontrol, dan kontrol mereferensikan delegasi penanganan aktivitas, tetapi delegasi ini tidak mereferensikan kembali ke halaman sehingga tidak ada siklus.

Masalahnya adalah ketika komponen C++ WinRT seperti Win2D digunakan oleh aplikasi .NET:

  • Halaman XAML adalah bagian dari aplikasi, jadi menggunakan pengumpulan sampah
  • Kontrol Win2D diimplementasikan di C++, jadi menggunakan penghitungan referensi
  • Delegasi penanganan peristiwa merupakan bagian dari aplikasi, sehingga menggunakan pengumpulan sampah dan menyimpan referensi yang kuat ke instans targetnya.

Siklus ada, tetapi objek Win2D yang berpartisipasi dalam siklus ini tidak menggunakan pengumpulan sampah .NET. Ini berarti pengumpul sampah tidak dapat melihat seluruh rantai, sehingga tidak dapat mendeteksi atau mengklaim kembali objek. Ketika ini terjadi, aplikasi harus membantu dengan secara eksplisit memutus siklus. Ini dapat dilakukan baik dengan merilis semua referensi dari halaman ke kontrol (seperti yang direkomendasikan di atas) atau dengan merilis semua referensi dari kontrol ke delegasi penanganan aktivitas yang mungkin menunjuk kembali ke halaman (menggunakan halaman Kejadian yang Tidak Dimuat untuk berhenti berlangganan semua penanganan aktivitas).