Zabránění únikům paměti

Při použití ovládacích prvků Win2D ve spravovaných aplikacích XAML je třeba dávat pozor na cykly referencí, které by mohly zabránit tomu, aby tyto ovládací prvky byly správně uvolněny správcem paměti.

Máte problém, pokud...

  • Používáte Win2D z jazyka .NET, jako je C# (ne nativní jazyk C++).
  • Používáte jeden z ovládacích prvků Win2D XAML:
  • Přihlásíte se k odběru událostí ovládacího prvku Win2D (např. Draw, CreateResources, SizeChanged...)
  • Vaše aplikace se pohybuje tam a zpět mezi více XAML stránkami.

Pokud jsou splněny všechny tyto podmínky, cyklus referenčních počtů zabrání tomu, aby kontrola Win2D byla kdy uvolněna z paměti. Nové prostředky Win2D jsou přidělovány pokaždé, když se aplikace přesune na jinou stránku, ale staré prostředky nejsou nikdy uvolněny, takže paměť uniká. Abyste tomu předešli, musíte přidat kód, který explicitně přeruší cyklus.

Postup opravy

Pokud chcete přerušit cyklus počtu odkazů a umožnit sběr paměti stránky:

  • Připojte událost Unloaded stránky XAML, která obsahuje ovládací prvek Win2D.
  • V obslužné rutině Unloaded volejte RemoveFromVisualTree na ovládací prvek Win2D.
  • V obslužné rutině Unloaded uvolněte všechny explicitní odkazy na ovládací prvek Win2D nastavením na null.

Příklad kódu:

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

Pracovní příklady najdete na kterékoli z demo stránek Příkladové galerie.

Postup testování úniku v cyklistice

Pokud chcete otestovat, jestli aplikace správně rozděluje cykly refcount, přidejte metodu finalizátoru na všechny stránky XAML, které obsahují ovládací prvky Win2D:

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

V konstruktoru App nakonfigurujte časovač, který zajistí, aby uvolňování paměti probíhalo v pravidelných intervalech.

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

Přejděte na stránku a pak od ní přejděte na jinou stránku. Pokud byly všechny cykly přerušené, během sekundy nebo dvou se zobrazí Debug.WriteLine výstup v podokně výstupu sady Visual Studio.

Všimněte si, že volání GC.Collect je rušivé a snižuje výkon, takže byste měli tento testovací kód odebrat, jakmile dokončíte testování úniku informací.

Krvavé detaily

Cyklus nastane, když objekt A má odkaz na B, ve stejnou dobu jako B má také odkaz na A. Nebo když A odkazuje B a B odkazuje na C, zatímco C odkazuje na A atd.

Při přihlášení k odběru událostí ovládacího prvku XAML je tento druh cyklu téměř nevyhnutelné:

  • Stránka XAML obsahuje odkazy na všechny ovládací prvky obsažené v něm
  • Ovládací prvky uchovávají odkazy na delegáty obslužné rutiny, kteří se přihlásili k odběru jejich událostí.
  • Každý delegát obsahuje odkaz na cílovou instanci.
  • Obslužné rutiny událostí jsou obvykle metody instance třídy stránky XAML, takže odkazy na cílovou instanci odkazují zpět na stránku XAML a vytvářejí cyklus.

Pokud jsou všechny zahrnuté objekty implementovány v .NET, tyto cykly nejsou problém, protože .NET obsahuje garbage collection, a algoritmus pro správu paměti dokáže identifikovat a uvolnit skupiny objektů, i když jsou propojené v cyklu.

Na rozdíl od rozhraní .NET spravuje jazyk C++ paměť počítáním odkazů, což nedokáže rozpoznat a uvolnit cykly objektů. I přes toto omezení nemají aplikace C++ používající Win2D žádný problém, protože obslužné rutiny událostí C++ ve výchozím nastavení uchovávají slabé místo silných odkazů na cílovou instanci. Proto stránka odkazuje na ovládací prvek a ovládací prvek odkazuje na delegáta obslužné rutiny události, tento delegát ale neodkazuje zpět na stránku, takže nevzniká žádný cyklus.

K problémem dochází v případě, že aplikace .NET používá komponentu WinRT C++, například Win2D:

  • Stránka XAML je součástí aplikace, takže používá uvolňování paměti.
  • Ovládací prvek Win2D je implementovaný v jazyce C++, takže používá počítání odkazů.
  • Delegát obslužné rutiny události je součástí aplikace, takže používá uvolňování paměti a uchovává silný odkaz na cílovou instanci.

Existuje cyklus, ale objekty Win2D, které se účastní tohoto cyklu, nepoužívají .NET správu paměti. To znamená, že garbage collector nedokáže vidět celý řetěz, takže nemůže rozpoznat ani uvolnit objekty. V takovém případě musí aplikace pomoct tím, že explicitně přeruší cyklus. To lze provést uvolněním všech odkazů ze stránky na ovládací prvek (jak je doporučeno výše) nebo uvolněním všech odkazů z ovládacího prvku na delegáty obslužné rutiny událostí, které mohou odkazovat zpět na stránku (pomocí události Unloaded pro zrušení odběru všech obslužných rutin událostí).