Frage

Ich habe in meiner WPF-Anwendung ein „angehängtes Verhalten“ erstellt, das es mir ermöglicht, den Tastendruck der Eingabetaste zu verarbeiten und zum nächsten Steuerelement zu wechseln.Ich nenne es EnterKeyTraversal.IsEnabled, und Sie können den Code auf meinem Blog sehen Hier.

Meine größte Sorge ist jetzt, dass ich möglicherweise einen Speicherverlust habe, da ich das PreviewKeyDown-Ereignis auf UIElements verarbeite und das Ereignis nie explizit „aushänge“.

Was ist der beste Ansatz, um dieses Leck zu verhindern (falls es tatsächlich eines gibt)?Sollte ich eine Liste der von mir verwalteten Elemente führen und das PreviewKeyDown-Ereignis im Application.Exit-Ereignis aushängen?Hat jemand mit angehängten Verhaltensweisen in seinen eigenen WPF-Anwendungen Erfolg gehabt und eine elegante Speicherverwaltungslösung gefunden?

War es hilfreich?

Lösung

Ich stimme nicht zu, DannySmurf

Einige WPF-Layoutobjekte können Ihren Speicher verstopfen und Ihre Anwendung sehr langsam machen, wenn sie nicht durch Garbage Collection erfasst werden.Daher halte ich die Wortwahl für richtig, Sie geben Speicher an Objekte weiter, die Sie nicht mehr verwenden.Sie gehen davon aus, dass die Elemente durch Garbage Collection erfasst werden, aber das ist nicht der Fall, da es irgendwo einen Verweis gibt (in diesem Fall in einem Ereignishandler).

Nun zu einer echten Antwort :)

Ich rate Ihnen, dies zu lesen Artikel zur WPF-Leistung auf MSDN

Das Nichtabbau von Ereignishandlern auf Objekten kann Objekte am Leben erhalten

Der Delegierter, dass ein Objekt an sein Ereignis übergeht, ist effektiv ein Hinweis auf dieses Objekt.Daher können Ereignishandler Objekte länger als erwartet am Leben erhalten.Bei der Durchführung eines Objekts, das sich registriert hat, um das Ereignis eines Objekts anzuhören, ist es wichtig, diesen Delegierten vor der Veröffentlichung des Objekts zu entfernen.Wenn Sie nicht benötigte Objekte am Leben erhalten, erhöht sich die Speicherverwendung der Anwendung.Dies gilt insbesondere dann, wenn das Objekt die Wurzel eines logischen Baums oder eines visuellen Baums ist.

Sie raten Ihnen, einen Blick darauf zu werfen Schwaches Ereignismuster

Eine andere Lösung wäre, die Event-Handler zu entfernen, wenn Sie mit einem Objekt fertig sind.Aber ich weiß, dass dieser Punkt bei angehängten Eigenschaften möglicherweise nicht immer klar ist.

Hoffe das hilft!

Andere Tipps

Abgesehen von der philosophischen Debatte sehe ich beim Betrachten des Blog-Beitrags des OP hier kein Leck:

ue.PreviewKeyDown += ue_PreviewKeyDown;

Ein harter Hinweis auf ue_PreviewKeyDown ist darin gespeichert ue.PreviewKeyDown.

ue_PreviewKeyDown ist ein STATIC Methode und kann nicht sein GCed.

Kein eindeutiger Hinweis darauf ue wird gespeichert, also steht der Speicherung nichts im Wege GCed.

Also...Wo ist das Leck?

Ja, ich weiß, dass Memory Leaks früher ein ganz anderes Thema waren.Aber bei verwaltetem Code könnte eine neue Bedeutung des Begriffs „Memory Leak“ passender sein …

Microsoft erkennt sogar, dass es sich um ein Speicherleck handelt:

Warum das WeakEvent-Muster implementieren?

Das Anhören für Ereignisse kann zu Speicherlecks führen.Die typische Technik zum Anhören eines Ereignisses besteht darin, die sprachspezifische Syntax zu verwenden, die einen Handler an einem Ereignis auf einer Quelle verbindet.Zum Beispiel in C#ist diese Syntax:Source.SomeEvent += new aEventHandler (MyeventHandler).

Diese Technik schafft eine starke Referenz von der Ereignisquelle auf den Ereignishörer.Normalerweise führt das Anbringen eines Ereignishandlers für einen Hörer dazu, dass der Hörer eine Objektlebensdauer hat, die von der Objektlebensdauer für die Quelle beeinflusst wird (es sei denn, der Ereignishandler wird explizit entfernt).Unter bestimmten Umständen möchten Sie jedoch, dass die Lebensdauer des Hörers nur durch andere Faktoren gesteuert wird, z. B. ob er derzeit zum visuellen Baum der Anwendung gehört, und nicht durch die Lebensdauer der Quelle.Wenn sich die Lebensdauer der Quellobjekte über die Objektlebensdauer des Hörers hinaus erstreckt, führt das normale Ereignismuster zu einem Speicherleck:Der Zuhörer wird länger am Leben erhalten als beabsichtigt.

Wir verwenden WPF für eine Client-App mit großen ToolWindows, die per Drag-Drop verschoben werden können, allen praktischen Dingen und alles kompatibel mit einem XBAP.Aber wir hatten das gleiche Problem mit einigen ToolWindows, die nicht durch Garbage Collection erfasst wurden.Dies lag daran, dass es immer noch von Ereignis-Listenern abhängig war.Dies stellt möglicherweise kein Problem mehr dar, wenn Sie Ihr Fenster schließen und Ihre App herunterfahren.Wenn Sie jedoch sehr große ToolWindows mit vielen Befehlen erstellen und alle diese Befehle immer wieder neu ausgewertet werden und die Benutzer Ihre Anwendung den ganzen Tag lang verwenden müssen.Ich kann es dir sagen..Es verstopft wirklich Ihren Speicher und die Reaktionszeit Ihrer App.

Außerdem finde ich es viel einfacher, meinem Manager zu erklären, dass wir einen Speicherverlust haben, als ihm zu erklären, dass einige Objekte aufgrund einiger Ereignisse, die bereinigt werden müssen, nicht im Garbage Collection gesammelt werden ;)

@Nick Ja, die Sache mit angehängten Verhaltensweisen ist, dass sie sich per Definition nicht im selben Objekt befinden wie die Elemente, deren Ereignisse Sie verarbeiten.

Ich denke, die Antwort liegt irgendwie in der Verwendung von WeakReference, aber ich habe keine einfachen Codebeispiele gesehen, die mir das erklären könnten.:) :)

Haben Sie darüber nachgedacht, das „Weak Event Pattern“ anstelle regulärer Ereignisse zu implementieren?

  1. Schwaches Ereignismuster in WPF
  2. Schwache Ereignismuster (MSDN)

Um meinen Kommentar zu John Fentons Beitrag hier zu erklären, ist meine Antwort.Sehen wir uns das folgende Beispiel an:

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        var b = new B();

        a.Clicked += b.HandleClicked;
        //a.Clicked += B.StaticHandleClicked;
        //A.StaticClicked += b.HandleClicked;

        var weakA = new WeakReference(a);
        var weakB = new WeakReference(b);

        a = null;
        //b = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        Console.WriteLine("a is alive: " + weakA.IsAlive);
        Console.WriteLine("b is alive: " + weakB.IsAlive);
        Console.ReadKey();
    }


}

class A
{
    public event EventHandler Clicked;
    public static event EventHandler StaticClicked;
}

class B
{
    public void HandleClicked(object sender, EventArgs e)
    {
    }

    public static void StaticHandleClicked(object sender, EventArgs e)
    {
    }
}

Wenn Sie haben

a.Clicked += b.HandleClicked;

und nur b auf null setzen, beide Referenzen schwachA und schwachB bleiben am Leben!Wenn Sie nur a auf Null setzen, bleibt b am Leben, aber nicht a (was beweist, dass John Fenton falsch liegt, wenn er angibt, dass im Ereignisanbieter eine harte Referenz gespeichert ist – in diesem Fall a).

Dies führte mich zu der FALSCHEN Schlussfolgerung

a.Clicked += B.StaticHandleClicked;

würde zu einem Leck führen, weil ich dachte, die Instanz von a würde vom statischen Handler behalten.Dies ist nicht der Fall (testen Sie mein Programm).Bei statischen Event-Handlern oder Events ist es umgekehrt.Wenn du schreibst

A.StaticClicked += b.HandleClicked;

Es wird auf b verwiesen.

Stellen Sie sicher, dass Elemente, die auf Ereignisse verweisen, sich in dem Objekt befinden, auf das sie verweisen, z. B. Textfelder im Formularsteuerelement.Oder wenn das nicht verhindert werden kann.Erstellen Sie ein statisches Ereignis für eine globale Hilfsklasse und überwachen Sie dann die globale Hilfsklasse auf Ereignisse.Wenn diese beiden Schritte nicht ausgeführt werden können, versuchen Sie es mit einer WeakReference. Sie eignen sich normalerweise perfekt für diese Situationen, sind jedoch mit einem Mehraufwand verbunden.

Ich habe gerade deinen Blogbeitrag gelesen und denke, dass du einen irreführenden Rat bekommen hast, Matt.Wenn es eine gibt tatsächlich Erinnerung Leck Dann handelt es sich hier um einen Fehler im .NET Framework, den Sie nicht unbedingt in Ihrem Code beheben können.

Was Sie (und der Poster auf Ihrem Blog) hier meines Erachtens eigentlich meinen, ist nicht wirklich ein Leak, sondern eher ein andauernder Speicherverbrauch.Das ist nicht dasselbe.Um es klar auszudrücken: Bei verlorenem Speicher handelt es sich um Speicher, der von einem Programm reserviert, dann aufgegeben wird (d. h. ein Zeiger bleibt baumeln) und anschließend nicht mehr freigegeben werden kann.Da der Speicher in .NET verwaltet wird, ist dies theoretisch unmöglich.Es ist jedoch möglich, dass ein Programm immer mehr Speicher reserviert, ohne dass Verweise darauf den Gültigkeitsbereich verlassen (und für die Garbage Collection infrage kommen).Dieser Speicher ist jedoch nicht durchgesickert.Der GC gibt es an das System zurück, sobald Ihr Programm beendet wird.

Also.Um Ihre Frage zu beantworten: Ich glaube nicht, dass Sie hier tatsächlich ein Problem haben.Sie haben mit Sicherheit keinen Speicherverlust, und Ihrem Code zufolge müssen Sie sich meiner Meinung nach auch keine Sorgen machen, was den Speicherverbrauch angeht.Solange Sie sicherstellen, dass Sie diesen Ereignishandler nicht wiederholt zuweisen, ohne ihn jemals wieder zuzuweisen (d. h. dass Sie ihn entweder immer nur einmal festlegen oder ihn bei jeder Zuweisung genau einmal entfernen), was Sie scheinen es zu tun, Ihr Code sollte in Ordnung sein.

Es scheint, als wäre das der Ratschlag, den Ihnen der Autor Ihres Blogs geben wollte, aber er benutzte dieses alarmierende Arbeitsleck, ein beängstigendes Wort, dessen wahre Bedeutung viele Programmierer in der verwalteten Welt jedoch vergessen haben;das trifft hier nicht zu.

@Arcturus:

...Verschließen Sie Ihren Speicher und machen Sie Ihre Anwendung sehr langsam, wenn sie nicht Müll gesammelt sind.

Das ist völlig offensichtlich und ich widerspreche nicht.Jedoch:

... Sie haben den Speicher, das Sie nicht mehr verwenden ...Weil es einen Hinweis auf sie gibt.

„Einem Programm wird Speicher zugewiesen, und dieses Programm verliert anschließend aufgrund von Fehlern in der Programmlogik die Möglichkeit, darauf zuzugreifen“ (Wikipedia, „Speicherleck“)

Wenn ein aktiver Verweis auf ein Objekt vorhanden ist, auf das Ihr Programm zugreifen kann, dann per Definition Es geht kein Speicher verloren.Ein Leck bedeutet, dass das Objekt nicht mehr zugänglich ist (für Sie oder das Betriebssystem/Framework) und nicht freigegeben wird für die Lebensdauer der aktuellen Sitzung des Betriebssystems.Dies ist hier nicht der Fall.

(Es tut mir leid, ein semantischer Nazi zu sein...Vielleicht bin ich ein bisschen altmodisch, aber Leak hat eine ganz bestimmte Bedeutung.Heutzutage neigen die Leute dazu, „Speicherverlust“ zu verwenden, um alles zu bezeichnen, was 2 KB mehr Speicher verbraucht, als sie wollen ...)

Aber wenn Sie einen Event-Handler nicht freigeben, wird das Objekt, an das er angehängt ist, natürlich erst freigegeben, wenn der Speicher Ihres Prozesses beim Herunterfahren vom Garbage Collector zurückgefordert wird.Aber dieses Verhalten ist völlig zu erwarten, im Gegensatz zu dem, was Sie offenbar andeuten.Wenn Sie erwarten, dass ein Objekt zurückgefordert wird, müssen Sie alles entfernen, was die Referenz am Leben erhalten könnte, einschließlich Ereignishandlern.

Wahr, wahr,

Du hast natürlich recht..Aber es wird eine ganz neue Generation von Programmierern auf diese Welt geboren, die niemals unverwalteten Code berühren wird, und ich glaube, dass sich Sprachdefinitionen immer wieder neu erfinden werden.Speicherlecks in WPF unterscheiden sich in dieser Hinsicht von beispielsweise C/Cpp.

Oder natürlich gegenüber meinen Vorgesetzten, ich habe es als Speicherleck bezeichnet.gegenüber meinen Kollegen habe ich es als Leistungsproblem bezeichnet!

Was Matts Problem betrifft, könnte es sich um ein Leistungsproblem handeln, das Sie möglicherweise angehen müssen.Wenn Sie nur ein paar Bildschirme verwenden und diese Bildschirmsteuerungen zu Singletons machen, wird dieses Problem möglicherweise überhaupt nicht auftreten ;).

Nun, das (den Manager-Teil) kann ich auf jeden Fall verstehen und mitfühlen.

Aber wie auch immer Microsoft es nennt, ich halte eine „neue“ Definition nicht für angemessen.Es ist kompliziert, weil wir nicht in einer zu 100 % verwalteten Welt leben (auch wenn Microsoft gerne so tut, als ob wir das tun würden, lebt Microsoft selbst nicht in einer solchen Welt).Wenn Sie von einem Speicherverlust sprechen, könnte dies bedeuten, dass ein Programm zu viel Speicher verbraucht (das ist eine Benutzerdefinition), oder dass eine verwaltete Referenz erst beim Beenden freigegeben wird (wie hier) oder dass eine nicht verwaltete Referenz nicht ordnungsgemäß bereinigt wird up (das wäre ein echter Speicherverlust), oder dass nicht verwalteter Code, der von verwaltetem Code aufgerufen wird, Speicher verliert (ein weiterer echter Speicherverlust).

In diesem Fall ist es offensichtlich, was „Speicherverlust“ bedeutet, auch wenn wir ungenau sind.Aber es wird furchtbar langweilig, mit manchen Leuten zu reden, die jeden übermäßigen Verbrauch oder das Versäumnis, etwas zu sammeln, als Speicherverlust bezeichnen;Und es ist frustrierend, wenn diese Leute Programmierer sind, die es angeblich besser wissen.Ich denke, es ist irgendwie wichtig, dass Fachbegriffe eine eindeutige Bedeutung haben.Das Debuggen ist dann viel einfacher.

Ohnehin.Ich möchte nicht, dass daraus eine luftige Diskussion über Sprache wird.Ich sage nur...

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top