Partager via


Éviter les fuites de mémoire

Lorsque vous utilisez des contrôles Win2D dans des applications XAML managées, vous devez veiller à éviter les cycles de nombre de références qui pourraient empêcher ces contrôles d’être récupérés par le récupérateur de mémoire.

Vous avez un problème si...

  • Vous utilisez Win2D à partir d’un langage .NET tel que C# (pas C++ natif)
  • Vous utilisez l’un des contrôles XAML Win2D :
  • Vous vous abonnez aux événements du contrôle Win2D (par exempleDraw, CreateResources, SizeChanged...)
  • Votre application se déplace dans les deux sens entre plusieurs pages XAML

Si toutes ces conditions sont remplies, un cycle de nombre de références empêche le contrôle Win2D de passer dans le récupérateur de mémoire. Les nouvelles ressources Win2D sont allouées chaque fois que l’application passe à une page différente, mais les anciennes ne sont jamais libérées, ce qui provoque une fuite de mémoire. Pour éviter cela, vous devez ajouter du code pour interrompre explicitement le cycle.

Procédure de résolution

Pour interrompre le cycle du nombre de références et laisser votre page passer dans le récupérateur de mémoire :

  • Raccorder l’événement Unloaded de la page XAML qui contient le contrôle Win2D
  • Dans le gestionnaire Unloaded, appelez RemoveFromVisualTree sur le contrôle Win2D
  • Dans le gestionnaire Unloaded, relâchez (en définissant sur null) toutes les références explicites au contrôle Win2D

Exemple de code :

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

Pour obtenir des exemples de travail, consultez l’une des pages de démonstration Exemple de galerie.

Comment tester les fuites du cycle

Pour tester si votre application interrompt correctement les cycles de comptage de références, ajoutez une méthode finaliseur à toutes les pages XAML qui contiennent des contrôles Win2D :

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

Dans votre constructeur App, configurez un minuteur qui s’assure que le garbage collection se produit à intervalles réguliers :

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

Accédez à la page, puis quittez-la pour une autre page. Si tous les cycles ont été rompus, vous verrez la sortie Debug.WriteLine dans le volet de sortie de Visual Studio au cours d’une seconde ou deux.

Notez que l’appel à GC.Collect est perturbateur et nuit aux performances, vous devez donc supprimer ce code de test dès que vous avez fini de tester les fuites !

Les détails sanglants

Un cycle se produit lorsqu’un objet A référence B, en même temps que B référence également A. Ou quand A référence B et B référence C, tandis que C référence A, etc.

Lors de l’abonnement aux événements d’un contrôle XAML, ce type de cycle est presque inévitable :

  • La page XAML possède des références à tous les contrôles qu’elle contient
  • Les contrôles contiennent des références aux délégués du gestionnaire qui ont été abonnés à leurs événements
  • Chaque délégué contient une référence à son instance cible
  • Les gestionnaires d’événements sont généralement des méthodes d’instance de la classe de page XAML. Par conséquent, leurs références d’instance cible pointent vers la page XAML, créant un cycle

Si tous les objets impliqués sont implémentés dans .NET, ces cycles ne sont pas un problème, car .NET intègre un vidage de la mémoire, et l’algorithme de garbage collection est en mesure d’identifier et de récupérer des groupes d’objets même s’ils sont liés dans un cycle.

Contrairement à .NET, C++ gère la mémoire en comptant les références, ce qui ne permet pas de détecter et de récupérer des cycles d’objets. Malgré cette limitation, les applications C++ utilisant Win2D n’ont aucun problème, car les gestionnaires d’événements C++ ont la valeur par défaut pour contenir des références faibles plutôt que des références fortes à leur instance cible. Par conséquent, la page fait référence au contrôle et le contrôle fait référence au délégué du gestionnaire d’événements, mais ce délégué ne fait pas référence à la page afin qu’il n’y ait aucun cycle.

Le problème se produit lorsqu’un composant WinRT C++ tel que Win2D est utilisé par une application .NET :

  • La page XAML fait partie de l’application. Elle utilise donc le garbage collection
  • Le contrôle Win2D est implémenté en C++. Il utilise donc le comptage de références.
  • Le délégué du gestionnaire d’événements fait partie de l’application. Il utilise donc le garbage collection et contient une référence forte à son instance cible

Un cycle est présent, mais les objets Win2D participant à ce cycle n’utilisent pas le garbage collection de .NET. Cela signifie que le récupérateur de mémoire ne peut pas voir l’intégralité de la chaîne, de sorte qu’il ne peut pas détecter ou récupérer les objets. Lorsque cela se produit, l’application doit vous aider en cassant explicitement le cycle. Pour ce faire, vous pouvez publier toutes les références de la page au contrôle (comme recommandé ci-dessus) ou libérer toutes les références du contrôle aux délégués du gestionnaire d’événements qui peuvent pointer vers la page (à l’aide de l’événement Unloaded de page pour désabonner tous les gestionnaires d’événements).