Freigeben über


Vermeiden von Speicherlecks

Wenn Sie Win2D-Steuerelemente in verwalteten XAML-Anwendungen verwenden, müssen Sie darauf achten, dass Verweisanzahlzyklen vermieden werden, die verhindern können, dass diese Steuerelemente vom Garbage Collector eingesammelt werden.

Sie haben ein Problem, wenn...

Wenn alle diese Bedingungen erfüllt sind, verhindert ein Referenzzählerzyklus, dass das Win2D-Steuerelement jemals der Speicherbereinigung zum Opfer fällt. Neue Win2D-Ressourcen werden jedes Mal zugewiesen, wenn die App zu einer anderen Seite wechselt, aber die alten werden nie freigegeben, sodass Arbeitsspeicher verloren geht. Um dies zu vermeiden, müssen Sie Code hinzufügen, um den Zyklus explizit zu unterbrechen.

So beheben Sie es

Um den Referenzzählzyklus zu unterbrechen und zu erlauben, dass Ihre Seite von der Speicherbereinigung aufgenommen wird:

  • Hängen Sie das Unloaded-Ereignis der XAML-Seite ein, die das Win2D-Steuerelement enthält.
  • Rufen Sie im Unloaded-Handler RemoveFromVisualTree für das Win2D-Steuerelement auf.
  • Geben Sie im Unloaded-Handler alle expliziten Verweise auf das Win2D-Steuerelement frei (durch Festlegen auf null)

Beispielcode:

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

Arbeitsbeispiele finden Sie auf einer beliebigen Demoseite der Beispielgalerie.

So testen Sie auf Lecks im Kreislauf

Um zu testen, ob Ihre Anwendung Refcount-Zyklen ordnungsgemäß unterbricht, fügen Sie allen XAML-Seiten, die Win2D-Steuerelemente enthalten, eine Finalizer-Methode hinzu.

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

Richten Sie in Ihrem App-Konstruktor einen Timer ein, der sicherstellt, dass die Garbage Collection in regelmäßigen Abständen erfolgt:

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

Navigieren Sie zu der Seite, und wechseln Sie dann zu einer anderen Seite. Wenn alle Zyklen unterbrochen wurden, wird die Ausgabe Debug.WriteLine innerhalb von ein bis zwei Sekunden im Visual Studio-Ausgabebereich angezeigt.

Beachten Sie, dass das Aufrufen GC.Collect störend ist und die Leistung beeinträchtigt, daher sollten Sie diesen Testcode entfernen, sobald Sie den Test für Lecks abgeschlossen haben!

Die blutigen Details

Ein Zyklus tritt auf, wenn ein Objekt A einen Verweis auf B hat, während B auch einen Verweis auf A hat. Oder wenn A auf B verweist und B auf C verweist, während C auf A verweist, usw.

Beim Abonnieren von Ereignissen eines XAML-Steuerelements ist so ein Zyklus ziemlich unvermeidlich.

  • XAML-Seite enthält Verweise auf alle darin enthaltenen Steuerelemente.
  • Steuerelemente enthalten Verweise auf die Handlerdelegats, die ihre Ereignisse abonniert haben
  • Jeder Delegate hält einen Verweis auf seine Zielinstanz.
  • Ereignishandler sind in der Regel Instanzmethoden der XAML-Seitenklasse, sodass ihre Zielinstanzverweise auf die XAML-Seite verweisen und einen Zyklus bilden.

Wenn alle beteiligten Objekte in .NET implementiert sind, sind solche Zyklen kein Problem, da .NET über eine Speicherbereinigung verfügt. Der Speicherbereinigungs-Algorithmus ist in der Lage, Gruppen von Objekten zu identifizieren und freizugeben, auch wenn sie in einem Zyklus verknüpft sind.

Im Gegensatz zu .NET verwaltet C++ den Speicher durch Referenzzählung, die keine Objektzyklen erkennen und auflösen kann. Trotz dieser Einschränkung haben C++-Apps, die Win2D verwenden, kein Problem, da C++-Ereignishandler standardmäßig schwache und keine starken Verweise auf ihre Zielinstanz enthalten. Daher verweist die Seite auf das Steuerelement, und das Steuerelement verweist auf den Ereignishandlerdelegaten, aber dieser Delegat verweist nicht zurück auf die Seite, sodass es keinen Zyklus gibt.

Das Problem tritt auf, wenn eine C++-WinRT-Komponente wie Win2D von einer .NET-Anwendung verwendet wird:

  • Die XAML-Seite ist Teil der Anwendung, verwendet daher die Garbage Collection.
  • Das Win2D-Steuerelement wird in C++ implementiert, daher erfolgt die Referenzzählung.
  • Der Ereignishandlerdelegat ist Teil der Anwendung, verwendet also garbage collection und enthält einen starken Verweis auf seine Zielinstanz.

Ein Zyklus ist vorhanden, aber die win2D-Objekte, die an diesem Zyklus teilnehmen, verwenden keine .NET Garbage Collection. Dies bedeutet, dass der Garbage Collector nicht die gesamte Kette sehen kann, sodass er die Objekte nicht erkennen oder freigeben kann. In diesem Fall muss die Anwendung helfen, indem der Zyklus explizit abgebrochen wird. Dazu können Sie entweder alle Verweise von der Seite auf das Steuerelement freigeben (wie oben empfohlen) oder alle Verweise vom Steuerelement auf Ereignishandlerdelegaten freigeben, die möglicherweise auf die Seite zurückverweisen (indem Sie das Unloaded-Ereignis der Seite verwenden, um alle Ereignishandler abzubestellen).