Delen via


Geheugenlekken voorkomen

Wanneer u Win2D-besturingselementen gebruikt in beheerde XAML-toepassingen, moet u ervoor zorgen dat er geen referentiecycli ontstaan die kunnen voorkomen dat deze besturingselementen door de garbagecollector worden vrijgemaakt.

U hebt een probleem als...

  • U gebruikt Win2D vanuit een .NET-taal zoals C# (niet systeemeigen C++)
  • U gebruikt een van de Win2D XAML-besturingselementen:
  • U abonneert u op gebeurtenissen van het Besturingselement Win2D (bijvoorbeeld Draw, CreateResources, SizeChanged...)
  • Uw app wordt heen en weer verplaatst tussen meer dan één XAML-pagina

Als aan al deze voorwaarden wordt voldaan, verhindert een referentietelling cyclus dat het Win2D-besturingselement ooit wordt verzameld door de vuilnisopruimer. Nieuwe Win2D-resources worden telkens toegewezen wanneer de app naar een andere pagina wordt verplaatst, maar de oude resources worden nooit vrijgemaakt zodat er geheugen wordt gelekt. Om dit te voorkomen, moet u code toevoegen om de cyclus expliciet te verbreken.

Het probleem oplossen

Als je de cyclus voor het aantal verwijzingen wilt doorbreken en je pagina door de garbage collector wilt laten verzamelen:

  • Sluit de Unloaded gebeurtenis aan van de XAML-pagina die het Win2D-besturingselement bevat
  • Roep in de Unloaded-handler RemoveFromVisualTree aan op het Win2D-besturingselement
  • Laat in de Unloaded handler eventuele expliciete verwijzingen naar het Win2D-besturingselement los (door deze in te stellen op null)

Voorbeeldcode:

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

Zie een van de demopagina's van de voorbeeldgalerie voor werkvoorbeelden.

Hoe te testen op lekken in de cyclus

Als u wilt testen of uw toepassing refcount-cycli correct onderbreekt, voegt u een finalizer-methode toe aan XAML-pagina's die Win2D-besturingselementen bevatten:

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

Stel in uw App constructor een timer in die ervoor zorgt dat garbage collection regelmatig wordt uitgevoerd.

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

Navigeer naar de pagina en ga er vervolgens vandaan naar een andere pagina. Als alle cycli zijn verbroken, ziet u binnen een of twee seconden Debug.WriteLine uitvoer in het uitvoervenster van Visual Studio.

Houd er rekening mee dat het aanroepen GC.Collect verstorend is en de prestaties pijn doet, dus u moet deze testcode verwijderen zodra u klaar bent met testen op lekken.

De gruwelijke details

Een cyclus treedt op wanneer een object A een verwijzing naar B heeft, op hetzelfde moment als B ook een verwijzing naar A heeft. Of wanneer A verwijst naar B en B verwijst naar C, terwijl C verwijst naar A, enzovoort.

Wanneer u zich abonneert op gebeurtenissen van een XAML-besturingselement, is dit soort cyclus vrijwel onvermijdelijk:

  • Een XAML-pagina bevat verwijzingen naar alle controls binnen het document.
  • Besturingselementen bevatten verwijzingen naar de handler-gemachtigden die zijn geabonneerd op hun gebeurtenissen
  • Iedere gedelegeerde bevat een verwijzing naar zijn doelinstantie.
  • Evenementhandlers zijn meestal instantiemethoden van de XAML-pagina-klasse, zodat hun doelinstanties terug verwijzen naar de XAML-pagina, waardoor een cyclus wordt gecreëerd.

Als alle betrokken objecten in .NET worden geïmplementeerd, zijn dergelijke cycli geen probleem omdat .NET "garbage collection" gebruikt en het garbage collection-algoritme in staat is groepen objecten te identificeren en vrij te maken, zelfs als ze in een cyclus gekoppeld zijn.

In tegenstelling tot .NET beheert C++ het geheugen op basis van het tellen van verwijzingen, waardoor cycli van objecten niet kunnen worden gedetecteerd en vrijgemaakt. Ondanks deze beperking hebben C++-apps die gebruikmaken van Win2D geen probleem omdat C++-gebeurtenishandlers in plaats van sterke, zwakke verwijzingen naar hun doelinstanties bevatten. De pagina verwijst daarom naar het besturingselement en het besturingselement verwijst naar de delegate van de gebeurtenishandler, maar deze delegate verwijst niet terug naar de pagina, waardoor er geen cyclus ontstaat.

Het probleem treedt op wanneer een C++ WinRT-onderdeel, zoals Win2D, wordt gebruikt door een .NET-toepassing:

  • De XAML-pagina maakt deel uit van de toepassing, dus maakt gebruik van garbage collection.
  • Het Win2D-besturingselement wordt geïmplementeerd in C++ en gebruikt referentietelling.
  • De gedelegeerde gebeurtenishandler maakt deel uit van de toepassing, dus maakt gebruik van vuilnisophaling en bevat een sterke verwijzing naar het doelexemplaar.

Er is een cyclus aanwezig, maar de Win2D-objecten die aan deze cyclus deelnemen, maken geen gebruik van .NET garbagecollection. Dit betekent dat de garbagecollector de hele keten niet kan zien, zodat de objecten niet kunnen worden gedetecteerd of vrijgemaakt. Wanneer dit gebeurt, moet de toepassing helpen door de cyclus expliciet te verbreken. U kunt dit doen door alle verwijzingen van de pagina naar het besturingselement uit te brengen (zoals hierboven wordt aanbevolen) of door alle verwijzingen van het besturingselement naar gedelegeerden van de gebeurtenis-handler uit te brengen die naar de pagina kunnen verwijzen (met behulp van de gebeurtenis Niet-geladen pagina om alle gebeurtenis-handlers af te melden).