13 Stimmen

Was ist der beste Weg, um Speicherlecks in einer WPF PRISM/MVVM-Anwendung zu vermeiden

Ich habe eine WPF-Anwendung basierend auf PRISM, die das MVVM-Muster nutzt.

Ich habe festgestellt, dass gelegentlich meine View-Modelle, Ansichten und alles, was mit ihnen verbunden ist, lange Zeit über ihr beabsichtigtes Lebensdauer hinaus verweilen.

Ein Leck betraf das Abonnieren von CollectionChanged an einer Sammlung, die zu einem injizierten Dienst gehört, ein anderes betraf das Nicht-Aufrufen der Stop-Methode auf einem DispatcherTimer, und ein weiteres erforderte, dass eine Sammlung ihrer Elemente geleert wird.

Ich denke, die Verwendung eines CompositePresentationEvent bevorzugen, um CollectionChanged zu abonnieren, aber in den anderen Szenarien neige ich dazu, IDisposable zu implementieren und die Ansichten die Dispose-Methode der View-Modelle aufrufen zu lassen.

Aber dann muss etwas die Ansicht darüber informieren, wann Dispose auf dem View-Model aufgerufen werden soll, was noch unattraktiver wird, wenn die Komplexität der Ansichten zunimmt und sie anfangen, Unterransichten einzuschließen.

Was halten Sie für den besten Ansatz, um View-Modelle zu handhaben, um sicherzustellen, dass sie keinen Speicher verlieren?

Vielen Dank im Voraus.

Ian

14voto

Anderson Imes Punkte 25252

Ich kann dir sagen, dass ich 100% des Schmerzes, den du erlebt hast, erlebt habe. Wir sind sozusagen Memory-Leak-Brüder, glaube ich.

Leider ist das Einzige, was ich hier herausgefunden habe, etwas sehr Ähnliches zu dem, was du denkst.

Was wir gemacht haben, ist eine angefügte Eigenschaft zu erstellen, die eine Ansicht auf sich selbst anwenden kann, um einen Handler an den ViewModel zu binden:

...

Dann hat unser ViewModel einfach eine Methode vom Typ Action:

public MyVM : ViewModel
{
     public Action CloseAction
     {
          get { return CloseActionInternal; }
     }

     private void CloseActionInternal()
     {
          //TODO: Stoppen von Timern, Aufräumen, usw.
     }
}

Wenn meine Schließmethode ausgelöst wird (wir haben einige Möglichkeiten, dies zu tun... es handelt sich dabei um eine TabControl-Benutzeroberfläche mit "X" in den Registerkartenüberschriften und ähnliches), überprüfe ich einfach, ob diese Ansicht sich mit der angefügten Eigenschaft registriert hat. Wenn ja, rufe ich die dort referenzierte Methode auf.

Es ist ein ziemlich umständlicher Weg, einfach zu überprüfen, ob der DataContext einer Ansicht IDisposable ist, aber es fühlte sich damals besser an. Das Problem beim Überprüfen des DataContext besteht darin, dass Sie möglicherweise Unter-ViewModels haben, die ebenfalls diese Steuerung benötigen. Sie müssten sicherstellen, dass Ihre ViewModels diesen Dispose-Aufruf weiterleiten oder alle Ansichten im Diagramm überprüfen und prüfen, ob ihre DataContexts IDisposable sind (ärgern).

Ich habe das Gefühl, dass hier etwas fehlt. Es gibt einige andere Frameworks, die versuchen, dieses Szenario auf andere Weise zu mildern. Sie sollten vielleicht einen Blick auf Caliburn werfen. Dort gibt es ein System zur Behandlung dieses Problems, bei dem ein ViewModel sich aller Unter-ViewModels bewusst ist und dies ermöglicht ihm, automatisch nach vorne zu verketten. Insbesondere gibt es ein Interface namens ISupportCustomShutdown (Ich glaube, so heißt es), das hilft, dieses Problem zu mildern.

Das Beste, was ich jedoch getan habe, ist sicherzustellen, dass Sie gute Memory-Leak-Tools wie den Redgate Memory Profiler verwenden, die Ihnen helfen, den Objektgraphen zu visualisieren und das Wurzelobjekt zu finden. Wenn es Ihnen gelungen ist, dieses DispatchTimer-Problem zu identifizieren, nehme ich an, dass Sie dies bereits tun.

Bearbeiten: Ich habe etwas Wichtiges vergessen. Es gibt ein potenzielles Memory-Leak, verursacht durch einen der Ereignishandler in DelegateCommand. Hier ist ein Thread dazu auf Codeplex, der das erklärt. http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4065

Die neueste Version des Prism (v2.1) hat dies behoben. (http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en).

2voto

Ian Oakes Punkte 9986

Meine bisherigen Erkenntnisse...

Zusätzlich zu PRISM, Unity, WPF und MVVM verwenden wir auch das Entity Framework und das Xceed-Datenraster. Das Speicherprofil wurde mit dotTrace erstellt.

Ich habe schließlich die IDisposable auf einer Basisklasse für meine Ansichtsmodelle implementiert, wobei die Dispose(bool)-Methode als virtual deklariert wird, um den Unterklassen die Möglichkeit zu geben, auch aufzuräumen. Da jedes Ansichtsmodell in unserer Anwendung einen Kindcontainer von Unity erhält, entsorgen wir diesen ebenfalls, um sicherzustellen, dass das ObjectContext von EF aus dem Geltungsbereich gelangt. Dies war unsere Hauptquelle für Speicherlecks.

Das Ansichtsmodell wird innerhalb einer expliziten CloseView(UserControl)-Methode auf einer Basiskontrollerklasse entsorgt. Es sucht nach einem IDisposable im DataContext der Ansicht und ruft Dispose darauf auf.

Das Xceed-Datenraster scheint einen großen Anteil an den Lecks zu verursachen, insbesondere in lang laufenden Ansichten. Jede Ansicht, die die ItemSource des Datenrasters durch Zuweisen einer neuen Sammlung aktualisiert, sollte Clear() auf der vorhandenen Sammlung aufrufen, bevor sie die neue Zuordnung vornimmt.

Seien Sie vorsichtig mit dem Entity Framework und vermeiden Sie lange laufende Objektkontexte. Es ist sehr unflexibel, wenn es um große Sammlungen geht, auch wenn Sie die Sammlung entfernt haben. Wenn das Tracking eingeschaltet ist, behält es eine Referenz zu jedem Element in der Sammlung bei, selbst wenn Sie nicht mehr an sie hängen.

Wenn Sie das Element nicht aktualisieren müssen, rufen Sie es mit MergeOption.NoTracking ab, insbesondere in langlaufenden Ansichten, die an Sammlungen binden.

Vermeiden Sie Ansichten mit langer Lebensdauer, halten Sie nicht an ihnen fest, wenn sie nicht sichtbar sind. Dies wird Ihnen besonders Kopfschmerzen bereiten, wenn sie ihre Daten in regelmäßigen Abständen aktualisieren, wenn sie sichtbar sind.

Wenn Sie CellContentTemplates auf der Xceed-Spalte verwenden, verwenden Sie keine dynamischen Ressourcen, da die Ressource eine Referenz auf die Zelle enthält, die ihrerseits die gesamte Ansicht am Leben erhält.

Wenn Sie CellEditor auf der Xceed-Spalte verwenden und die Ressource in einem externen Ressourcenwörterbuch gespeichert ist, fügen Sie x:Shared="False" zur Ressource hinzu, die den CellEditor enthält. Dadurch wird sichergestellt, dass Sie jedes Mal eine frische Kopie erhalten, wobei die alte ordnungsgemäß entfernt wird.

Seien Sie vorsichtig beim Binden des DelegateCommand an Elemente innerhalb des Xceed-Datenrasters. Wenn Sie beispielsweise einen Löschbutton in der Zeile haben, der an einen Befehl bindet, stellen Sie sicher, dass Sie die Sammlung, die die ItemsSource enthält, löschen, bevor Sie die Ansicht schließen. Wenn Sie die Sammlung aktualisieren, müssen Sie auch den Befehl neu initialisieren, da der Befehl eine Referenz zu jeder Zeile behält.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X