Dela via


Undvika minnesläckor

När du använder Win2D-kontroller i hanterade XAML-program måste du vara noga med att undvika referensantalscykler som kan förhindra att dessa kontroller någonsin återvinns av skräpinsamlaren.

Du har problem om...

Om alla dessa villkor uppfylls, kommer en referensräkningscykel att förhindra att Win2D-kontrollen någonsin blir sopinsamlad. Nya Win2D-resurser allokeras varje gång appen flyttas till en annan sida, men de gamla frigörs aldrig så att minnet läcker ut. För att undvika detta måste du lägga till kod för att uttryckligen bryta cykeln.

Så här åtgärdar du det

Så här bryter du referensantalets cykel och låter sidan vara skräpinsamling:

  • Koppla Unloaded-händelsen för XAML-sidan som innehåller Win2D-kontrollen
  • I Unloaded hanteraren, anropa RemoveFromVisualTree på Win2D-kontrollen
  • Unloaded I hanteraren släpper du (genom att ange till null) alla explicita referenser till Win2D-kontrollen

Exempelkod:

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

Exempel finns på någon av demosidorna i Exempelgalleriet .

Så här testar du för cykelläckor

Om du vill testa om programmet bryter refcount-cykler korrekt lägger du till en finalizer-metod till alla XAML-sidor som innehåller Win2D-kontroller:

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

App I konstruktorn konfigurerar du en timer som ser till att skräpinsamling sker med jämna mellanrum:

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

Gå till sidan och gå sedan bort från den till någon annan sida. Om alla cykler har brutits visas Debug.WriteLine utdata i Visual Studio-utdatafönstret inom en sekund eller två.

Observera att anrop GC.Collect är störande och skadar prestanda, så du bör ta bort den här testkoden så snart du har slutfört testningen för läckor!

De grisiga detaljerna

En cykel inträffar när ett objekt A har en referens till B, samtidigt som B också har en referens till A. Eller när A refererar till B och B refererar till C, medan C refererar till A osv.

När du prenumererar på händelser i en XAML-kontroll är den här typen av cykel ganska oundviklig:

  • XAML-sidan innehåller referenser till alla kontroller som finns i den
  • Kontroller innehåller referenser till de hanterardelegater som har prenumererat på deras händelser
  • Varje ombud har en referens till sin målinstans
  • Händelsehanterare är vanligtvis instansmetoder för XAML-sidklassen, så deras målinstansreferenser pekar tillbaka till XAML-sidan och skapar en cykel

Om alla objekt som ingår implementeras i .NET är sådana cykler inte ett problem eftersom .NET är skräpinsamling och algoritmen för skräpinsamling kan identifiera och återta grupper av objekt även om de är länkade i en cykel.

Till skillnad från .NET hanterar C++ minne genom referensräkning, vilket inte kan identifiera och frigöra objektcykler. Trots den här begränsningen har C++-appar som använder Win2D inga problem eftersom C++-händelsehanterare som standard håller svaga snarare än starka referenser till målinstansen. Därför refererar sidan till kontrollen och kontrollen refererar till händelsehanterarens delegat, men denna delegat refererar inte tillbaka till sidan så det finns ingen cykel.

Problemet uppstår när en C++ WinRT-komponent, till exempel Win2D, används av ett .NET-program:

  • XAML-sidan är en del av programmet och använder därför skräpinsamling.
  • Win2D-kontrollen implementeras i C++, så använder referensräkning
  • Händelsehanterardelegaten är en del av programmet och använder därför skräpsamling och håller en stark referens till målinstansen.

Det finns en cykel, men Win2D-objekten som deltar i den här cykeln använder inte .NET-skräpinsamling. Det innebär att skräpinsamlaren inte kan se hela kedjan, så den kan inte identifiera eller återta objekten. När detta inträffar måste programmet hjälpa till genom att uttryckligen bryta cykeln. Detta kan göras antingen genom att ta bort alla referenser från sidan till kontrollen (som rekommenderas ovan) eller genom att ta bort alla referenser från kontrollen till händelsehanteringsdelegater som kan peka tillbaka på sidan (med hjälp av Unloaded-händelsen på sidan för att avbryta prenumerationen på alla händelsehanterare).