Vermeiden von Speicherverlusten
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 erneut beansprucht werden.
Sie haben ein Problem, wenn…
- Sie Win2D aus einer .NET-Sprache wie C# (nicht systemeigenes C++) verwenden
- Sie eines der Win2D-XAML-Steuerelemente verwenden:
- Sie Ereignisse des Win2D-Steuerelements abonnieren (z. B.
Draw
,CreateResources
,SizeChanged
...) - Ihre App zwischen mehr als einer XAML-Seite hin und her wechselt
Wenn alle diese Bedingungen erfüllt sind, verhindert ein Referenzzählzyklus, dass das Win2D-Steuerelement jemals in die automatische Speicherbereinigung gelangt. 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.
Problemlösung
So unterbrechen Sie den Referenzzählzyklus und überlassen Ihre Seite der automatischen Speicherbereinigung:
- Verbinden des
Unloaded
Ereignisses der XAML-Seite, die das Win2D-Steuerelement enthält - Rufen Sie
Unloaded
imRemoveFromVisualTree
-Handler auf dem Win2D-Steuerelement auf - Geben Sie im
Unloaded
Handler alle expliziten Verweise auf das Win2D-Steuerelement frei (durch Festlegen aufnull
)
Codebeispiel:
void page_Unloaded(object sender, RoutedEventArgs e)
{
this.canvas.RemoveFromVisualTree();
this.canvas = null;
}
Arbeitsbeispiele finden Sie auf einer der Demoseiten des Beispielkatalogs.
So testen Sie Zykluslecks
Um zu testen, ob ihre Anwendung refcount-Zyklen ordnungsgemäß unterbricht, fügen Sie allen XAML-Seiten, die Win2D-Steuerelemente enthalten, eine Finalizermethode hinzu:
~MyPage()
{
System.Diagnostics.Debug.WriteLine("~" + GetType().Name);
}
Richten Sie in Ihrem App
Konstruktor einen Timer ein, der sicherstellt, dass die automatische Speicherbereinigung 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 Debug.WriteLine
die Ausgabe im Visual Studio-Ausgabebereich innerhalb einer oder zwei Sekunden angezeigt.
Beachten Sie, dass der Aufruf von GC.Collect
störend ist und die Leistung beeinträchtigt, daher sollten Sie diesen Testcode entfernen, sobald Sie den Test auf Lecks beendet haben!
Die Gory-Details
Ein Zyklus tritt auf, wenn ein Objekt A einen Verweis auf B hat, gleichzeitig wie B auch einen Verweis auf A hat. Oder wenn A auf B und B auf C verweist, während C auf A verweist usw.
Beim Abonnieren von Ereignissen eines XAML-Steuerelements ist diese Art von Zyklus ziemlich unvermeidlich:
- XAML-Seite enthält Verweise auf alle darin enthaltenen Steuerelemente.
- Steuerelemente enthalten Verweise auf die Handler-Delegierten, die ihre Ereignisse abonniert haben
- Jede Stellvertretung enthält einen Verweis auf die Zielinstanz.
- Ereignishandler sind in der Regel Instanzmethoden der XAML-Seitenklasse, sodass ihre Zielinstanzverweise auf die XAML-Seite verweisen und einen Zyklus erstellen.
Wenn alle beteiligten Objekte in .NET implementiert sind, stellen solche Zyklen kein Problem dar, da .NET automatische Speicherbereinigung einsetzt und der Algorithmus der automatischen Speicherbereinigung in der Lage ist, Gruppen von Objekten zu identifizieren und zurückzufordern, selbst wenn sie in einem Zyklus verbunden sind.
Im Gegensatz zu .NET verwaltet C++ speicherweise durch Zählvorgänge, die keine Zyklen von Objekten erkennen und freigeben können. 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 Ereignishandler-Delegat, aber dieser Delegat verweist nicht auf die Seite, sodass kein Zyklus vorhanden ist.
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 automatische Speicherbereinigung.
- Das Win2D-Steuerelement wird in C++ implementiert. Es wird also referenzgezählt.
- Der Ereignishandlerdelegat ist Teil der Anwendung, verwendet also die automatische Speicherbereinigung 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 automatische Speicherbereinigung. Dies bedeutet, dass der Garbage Collector die gesamte Kette nicht sehen kann, sodass er die Objekte nicht erkennen oder zurückfordern 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 Ereignishandlerdelegate freigeben, die auf die Seite verweisen können (mithilfe des Seiten-Unloaded-Ereignisses, um alle Ereignishandler abzubestellen).
Windows developer