Memóriavesztés elkerülése

Ha Win2D-vezérlőket használ felügyelt XAML-alkalmazásokban, ügyelni kell arra, hogy elkerülje a hivatkozásszám-ciklusokat, amelyek megakadályozhatják, hogy ezeket a vezérlőket a szemétgyűjtő valaha is visszanyerje.

Problémád van, ha...

Ha ezek a feltételek teljesülnek, a referenciaszám-ciklus megakadályozza, hogy a Win2D-vezérlő valaha is szemétgyűjtés alá kerüljön. Az új Win2D-erőforrások minden alkalommal ki vannak foglalva, amikor az alkalmazás egy másik oldalra kerül, de a régiek soha nem szabadulnak fel, így a memória kiszivárog. Ennek elkerülése érdekében kódot kell hozzáadnia a ciklus explicit megszakításához.

Javítási lehetőségek

Ha meg szeretné szakítani a hivatkozásszám ciklusát, és lehetővé szeretné tenni a lap szemétgyűjtését:

  • Unloaded A Win2D-vezérlőt tartalmazó XAML-oldal eseményének összekapcsolása
  • Unloaded A kezelőben hívja meg RemoveFromVisualTree a Win2D-vezérlőt
  • A Unloaded kezelőben engedje el a Win2D-vezérlőre mutató explicit hivatkozásokat úgy, hogy azokat beállítja null.

Példakód:

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

A működő példákért tekintse meg a Mintagyűjtemény bemutatóoldalait.

Hogyan teszteljük a ciklusszivárgásokat

Annak ellenőrzéséhez, hogy az alkalmazás helyesen szakítja-e meg az újraszámlálási ciklusokat, adjon hozzá egy véglegesítő metódust a Win2D-vezérlőket tartalmazó XAML-lapokhoz:

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

App A konstruktorban állítson be egy időzítőt, amely biztosítja, hogy a szemétgyűjtés rendszeres időközönként történjen:

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

Nyissa meg az oldalt, majd lépjen át egy másik oldalra. Ha az összes ciklus megszakadt, a Debug.WriteLine kimenet egy-két másodpercen belül megjelenik a Visual Studio kimeneti paneljén.

Vegye figyelembe, hogy a hívás GC.Collect zavaró és rontja a teljesítményt, ezért azonnal távolítsa el ezt a tesztkódot, amint befejezi a szivárgások tesztelését!

A véres részletek

Ciklus akkor fordul elő, ha az A objektum a B-re hivatkozik, miközben B is az A-ra hivatkozik. Vagy ha az A hivatkozik a B-re, a B hivatkozik a C-re, míg a C hivatkozik az A-ra, stb.

Ha feliratkozik egy XAML-vezérlő eseményeire, ez a fajta ciklus nagyjából elkerülhetetlen:

  • Az XAML-oldal a benne található összes vezérlőre hivatkozik
  • A vezérlők az eseményeikhez kapcsolt kezelési delegáltakra mutató hivatkozásokat tartalmaznak.
  • Minden meghatalmazott hivatkozik a célpéldányára
  • Az eseménykezelők általában az XAML oldal osztály példánymetódusai, így a célpéldány-hivatkozások visszairányulnak az XAML oldalra, és létrehoznak egy ciklust.

Ha az összes érintett objektum a .NET-ben van implementálva, az ilyen ciklusok nem jelentenek problémát, mert a .NET szemétgyűjtésre kerül, és a szemétgyűjtési algoritmus akkor is képes azonosítani és visszaigényelni az objektumcsoportokat, ha azok egy ciklusban vannak összekapcsolva.

A .NET-tel ellentétben a C++ a memóriát hivatkozásszámlálással kezeli, amely nem képes észlelni és visszanyerni az objektumok ciklusait. A korlátozás ellenére a Win2D-t használó C++ alkalmazásoknak nincs gondjuk, mivel a C++ eseménykezelők alapértelmezés szerint gyenge, nem pedig erős hivatkozásokat tartalmaznak a célpéldányukra. Ezért a lap a vezérlőre hivatkozik, a vezérlő pedig az eseménykezelő delegáltjára, de ez a delegált nem hivatkozik vissza a lapra, így nincs ciklus.

A probléma akkor jelentkezik, ha egy .NET-alkalmazás egy C++ WinRT-összetevőt, például Win2D-t használ:

  • Az XAML-oldal az alkalmazás része, ezért szemétgyűjtést használ
  • A Win2D-vezérlő a C++-ban van implementálva, ezért hivatkozásszámlálást használ
  • Az eseménykezelő delegáltja az alkalmazás része, ezért szemétgyűjtést használ, és erős hivatkozást tartalmaz a célpéldányra

Van egy ciklus, de a ciklusban részt vevő Win2D-objektumok nem .NET szemétgyűjtést használnak. Ez azt jelenti, hogy a szemétgyűjtő nem látja a teljes láncot, ezért nem tudja észlelni vagy visszanyerni az objektumokat. Ha ez történik, az alkalmazásnak segítenie kell a ciklus explicit megszakításával. Ezt úgy teheti meg, hogy a lapról a vezérlőre mutató összes hivatkozást felengedi (a fentieknek megfelelően), vagy az összes olyan hivatkozást felszabadítja a vezérlőből az eseménykezelő delegáltjai számára, amelyek a lapra mutathatnak (a lap kiürített eseményével leiratkozhat az összes eseménykezelőről).