it-swarm.com.de

Wann sollten schwache Referenzen in .Net verwendet werden?

Ich persönlich bin nicht auf eine Situation gestoßen, in der ich den WeakReference-Typ in .Net verwenden musste, aber die weit verbreitete Überzeugung scheint zu sein, dass er in Caches verwendet werden sollte. Dr. Jon Harrop sprach sich in seiner Frage Antwort bis diese sehr gut gegen die Verwendung von WeakReferences in Caches aus.

Ich habe auch oft gehört, dass AS3-Entwickler über die Verwendung schwacher Referenzen sprechen, um Speicherplatz zu sparen, aber aufgrund der Gespräche, die ich geführt habe, scheint dies die Komplexität zu erhöhen, ohne das beabsichtigte Ziel unbedingt zu erreichen, und das Laufzeitverhalten ist eher unvorhersehbar. So sehr, dass viele einfach aufgeben und stattdessen die Speichernutzung sorgfältiger verwalten/ihren Code so optimieren, dass er weniger speicherintensiv ist (oder mehr CPU-Zyklen und einen geringeren Speicherbedarf in Kauf nehmen).

Dr. Jon Harrop wies in seiner Antwort auch darauf hin, dass die schwachen .Net-Referenzen nicht weich sind und es bei gen0 eine aggressive Sammlung schwacher Referenzen gibt. Laut MSDN bieten lange schwache Referenzen das Potenzial, ein Objekt neu zu erstellen, but the state of the object remains unpredictable.!

Angesichts dieser Eigenschaften kann ich mir keine Situation vorstellen, in der schwache Referenzen nützlich wären, vielleicht könnte mich jemand aufklären?

57
theburningmonk

Ich habe in den folgenden drei realen Szenarien, die mir persönlich tatsächlich passiert sind, legitime praktische Anwendungen schwacher Referenzen gefunden:

Anwendung 1: Ereignishandler

Du bist ein Unternehmer. Ihr Unternehmen verkauft eine Funkenlinien Steuerung für WPF. Der Umsatz ist großartig, aber die Supportkosten bringen Sie um. Zu viele Kunden beschweren sich über CPU-Hogging und Speicherlecks, wenn sie durch Bildschirme mit spark Zeilen) scrollen. Das Problem ist, dass ihre App neue spark Zeilen als Sie kommen in Sicht, aber die Datenbindung verhindert, dass die alten Müll gesammelt werden. Was machen Sie?

Führen Sie eine schwache Referenz zwischen der Datenbindung und Ihrer Kontrolle ein, damit die Datenbindung allein nicht mehr verhindert, dass Ihre Kontrolle durch Müll gesammelt wird. Fügen Sie dann Ihrem Steuerelement einen Finalizer hinzu, der die Datenbindung beim Sammeln aufhebt.

Anwendung 2: Veränderbare Graphen

Du bist der nächste John Carmack. Sie haben eine geniale neue grafische Darstellung hierarchischer Unterteilungsflächen erfunden, mit der Tim Sweeneys Spiele wie eine Nintendo Wii aussehen. Natürlich werde ich Ihnen nicht sagen genau wie es funktioniert , aber alles dreht sich um diesen veränderlichen Graphen, in dem die Nachbarn eines Scheitelpunkts in einem Dictionary<Vertex, SortedSet<Vertex>> Gefunden werden können. Die Topologie des Diagramms ändert sich ständig, wenn der Player herumläuft. Es gibt nur ein Problem: Ihre Datenstruktur verliert während der Ausführung nicht erreichbare Untergraphen, und Sie müssen sie entfernen, da sonst Speicherplatz verloren geht. Glücklicherweise sind Sie ein Genie und wissen, dass es eine Klasse von Algorithmen gibt, die speziell zum Auffinden und Sammeln nicht erreichbarer Untergraphen entwickelt wurden: Garbage Collectors! Sie lesen Richard Jones 'ausgezeichnete Monographie zu diesem Thema , aber Sie sind ratlos und besorgt über Ihre bevorstehende Frist. Wie geht's?

Durch einfaches Ersetzen Ihres Dictionary durch eine schwache Hash-Tabelle können Sie den vorhandenen GC huckepack nehmen und Ihre nicht erreichbaren Untergraphen automatisch für Sie sammeln lassen! Zurück zum Blättern in Ferrari-Anzeigen.

Anwendung 3: Bäume schmücken

Sie hängen an einer Tastatur an der Decke eines zyklindrischen Raums. Sie haben 60 Sekunden Zeit, um einige GROSSE DATEN zu sichten, bevor Sie jemand findet. Sie wurden mit einem wunderschönen Stream-basierten Parser vorbereitet, der sich darauf stützt, dass der GC Fragmente von AST, nachdem sie analysiert wurden, sammelt. Sie stellen jedoch fest, dass Sie für jedes AST Node und du brauchst es schnell. Was machst du?

Sie können einen Dictionary<Node, Metadata> Verwenden, um jedem Knoten Metadaten zuzuordnen. Wenn Sie ihn jedoch nicht löschen, werden die starken Verweise des Wörterbuchs auf alte AST -Knoten) am Leben bleiben und Speicher verlieren Die Lösung ist eine schwache Hash-Tabelle , die nur schwache Verweise auf Schlüssel enthält, und Garbage sammelt Schlüsselwertbindungen, wenn der Schlüssel nicht mehr erreichbar ist. Dann als = AST Knoten werden nicht mehr erreichbar, sie werden durch Müll gesammelt und ihre Schlüsselwertbindung wird aus dem Wörterbuch entfernt, sodass die entsprechenden Metadaten nicht mehr erreichbar sind, sodass auch sie gesammelt werden. Dann müssen Sie alles tun, nachdem Ihre Hauptschleife beendet wurde wird durch die Entlüftungsöffnung nach oben geschoben und daran gedacht, sie zu ersetzen, sobald der Wachmann hereinkommt.

Beachten Sie, dass ich in allen drei realen Anwendungen, die mir tatsächlich passiert sind, wollte, dass der GC so aggressiv wie möglich sammelt. Deshalb sind dies legitime Anwendungen. Alle anderen liegen falsch.

40
Jon Harrop

Angesichts dieser Eigenschaften kann ich mir keine Situation vorstellen, in der schwache Referenzen nützlich wären, vielleicht könnte mich jemand aufklären?

Microsoft-Dokument Schwache Ereignismuster .

In Anwendungen ist es möglich, dass Handler, die an Ereignisquellen angehängt sind, nicht in Abstimmung mit dem Listener-Objekt zerstört werden, das den Handler an die Quelle angehängt hat. Diese Situation kann zu Speicherlecks führen. Windows Presentation Foundation (WPF) führt ein Entwurfsmuster ein, mit dem dieses Problem behoben werden kann, indem eine dedizierte Managerklasse für bestimmte Ereignisse bereitgestellt und eine Schnittstelle für Listener für dieses Ereignis implementiert wird. Dieses Entwurfsmuster ist als schwaches Ereignismuster bekannt.

...

Das Muster für schwache Ereignisse soll dieses Problem mit Speicherverlusten lösen. Das schwache Ereignismuster kann immer dann verwendet werden, wenn sich ein Listener für ein Ereignis registrieren muss, der Listener jedoch nicht explizit weiß, wann die Registrierung aufgehoben werden muss. Das schwache Ereignismuster kann auch verwendet werden, wenn die Objektlebensdauer der Quelle die nützliche Objektlebensdauer des Listeners überschreitet. (In diesem Fall wird der Nutzen von Ihnen bestimmt.) Das schwache Ereignismuster ermöglicht es dem Listener, sich für das Ereignis zu registrieren und es zu empfangen, ohne die Objektlebensdauer des Listeners in irgendeiner Weise zu beeinflussen. Tatsächlich bestimmt die implizite Referenz aus der Quelle nicht, ob der Listener für die Speicherbereinigung berechtigt ist. Die Referenz ist eine schwache Referenz, daher die Benennung des schwachen Ereignismusters und der zugehörigen APIs . Der Listener kann durch Müll gesammelt oder auf andere Weise zerstört werden, und die Quelle kann fortgesetzt werden, ohne dass nicht sammelbare Handler-Verweise auf ein jetzt zerstörtes Objekt erhalten bleiben.

19
ta.speot.is

Lassen Sie mich dies zuerst herausstellen und darauf zurückkommen:

Eine WeakReference ist nützlich, wenn Sie ein Objekt im Auge behalten möchten, Ihre Beobachtungen jedoch NICHT verhindern sollen, dass dieses Objekt erfasst wird

Beginnen wir also von vorne:

- entschuldige mich im Voraus für jede unbeabsichtigte Straftat, aber ich werde für einen Moment wieder auf "Dick and Jane" -Niveau zurückkehren, da man es dem Publikum nie sagen kann.

Wenn Sie also ein Objekt X haben - geben Sie es als Instanz von class Foo An -, kann es NICHT alleine leben (meistens wahr); Genauso wie "Kein Mensch ist eine Insel" gibt es nur wenige Möglichkeiten, wie ein Objekt zur Inselheit befördert werden kann - obwohl es in CLR-Sprache als GC-Wurzel bezeichnet wird. Ein GC-Root zu sein oder eine etablierte Kette von Verbindungen/Verweisen auf einen GC-Root zu haben, bestimmt im Grunde, ob Foo x = new Foo() Müll gesammelt wird oder nicht.

Wenn Sie weder durch Heap- noch durch Stack-Walking zu einer GC-Wurzel zurückkehren können, sind Sie effektiv verwaist und werden wahrscheinlich im nächsten Zyklus markiert/gesammelt.

Schauen wir uns an dieser Stelle einige schrecklich erfundene Beispiele an:

Zuerst unser Foo:

public class Foo 
{
    private static volatile int _ref = 0;
    public event EventHandler FooEvent;
    public Foo()
    {
        _ref++;
        Console.WriteLine("I am #{0}", _ref);
    }
    ~Foo()
    {
        Console.WriteLine("#{0} dying!", _ref--);
    }
}

Ziemlich einfach - es ist nicht threadsicher, versuchen Sie es also nicht, sondern behalten Sie eine grobe "Referenzanzahl" aktiver Instanzen und Dekremente bei, wenn diese abgeschlossen sind.

Schauen wir uns nun ein FooConsumer an:

public class NastySingleton
{
    // Static member status is one way to "get promoted" to a GC root...
    private static NastySingleton _instance = new NastySingleton();
    public static NastySingleton Instance { get { return _instance;} }

    // testing out "Hard references"
    private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
    // testing out "Weak references"
    private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();

    // Creates a strong link to Foo instance
    public void ListenToThisFoo(Foo foo)
    {
        _counter[foo] = 0;
        foo.FooEvent += (o, e) => _counter[foo]++;
    }

    // Creates a weak link to Foo instance
    public void ListenToThisFooWeakly(Foo foo)
    {
        WeakReference fooRef = new WeakReference(foo);
        _weakCounter[fooRef] = 0;
        foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
    }

    private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
    {
        Console.WriteLine("Derp");
    }
}

Wir haben also ein Objekt, das bereits ein eigenes GC-Stammverzeichnis ist (genauer gesagt, es wird über eine Kette direkt an die App-Domäne verwurzelt, in der diese Anwendung ausgeführt wird, aber das ist ein anderes Thema), das zwei Methoden hat des Festhaltens an einer Foo -Instanz - testen wir es:

// Our foo
var f = new Foo();

// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Würden Sie nun von oben erwarten, dass das Objekt, auf das f einmal Bezug genommen hat, "sammelbar" ist?

Nein, da es jetzt ein anderes Objekt gibt, das einen Verweis darauf enthält - das Dictionary in dieser statischen Instanz Singleton.

Ok, versuchen wir den schwachen Ansatz:

f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Wenn wir nun unseren Verweis auf das -Foo-, das einmal -f schlug, gibt es keine "harten" Verweise mehr auf das Objekt, so dass es sammelbar ist - das WeakReference, das vom schwachen Listener erstellt wurde, wird das nicht verhindern.

Gute Anwendungsfälle:

  • Ereignishandler (Obwohl zuerst gelesen: Schwache Ereignisse in C # )

  • Sie haben eine Situation, in der Sie eine "rekursive Referenz" verursachen würden (d. H. Objekt A bezieht sich auf Objekt B, das sich auf Objekt A bezieht, das auch als "Speicherleck" bezeichnet wird). (edit: derp, das stimmt natürlich nicht)

  • Sie möchten etwas an eine Sammlung von Objekten "senden", aber Sie möchten nicht das sein, was sie am Leben hält. Ein List<WeakReference> kann einfach gepflegt und sogar beschnitten werden, indem entfernt wird, wo ref.Target == null

13
JerKimball

(enter image description here

Wie logische Lecks, die wirklich schwer zu finden sind, während Benutzer nur bemerken, dass das Ausführen Ihrer Software über einen längeren Zeitraum immer mehr Speicher beansprucht und immer langsamer wird, bis sie neu starten? Ich nicht.

Überlegen Sie, was passiert, wenn Thing2 Ein solches Ereignis nicht ordnungsgemäß behandelt, wenn der Benutzer die oben genannte Anwendungsressource entfernen möchte:

  1. Zeiger
  2. Starke Referenzen
  3. Schwache Referenzen

... und unter welchem ​​dieser Fehler würde ein solcher Fehler wahrscheinlich beim Testen aufgefangen werden und unter welchem ​​nicht und wie ein Stealth-Fighter-Bug unter dem Radar fliegen würde. Geteiltes Eigentum ist häufiger als die meisten anderen eine unsinnige Idee.

4
user204677

Ein sehr anschauliches Beispiel für schwache Referenzen, die effektiv verwendet werden, ist ConditionalWeakTable , das vom DLR (unter anderem) verwendet wird, um zusätzliche "Mitglieder" an Objekte anzuhängen.

Sie möchten nicht, dass die Tabelle das Objekt am Leben erhält. Dieses Konzept könnte ohne schwache Referenzen einfach nicht funktionieren.

Es scheint mir jedoch, dass alle Verwendungen für schwache Referenzen lange nach dem Hinzufügen zur Sprache erfolgten, da schwache Referenzen seit Version 1.1 Teil von .NET sind. Es scheint nur etwas zu sein, das Sie hinzufügen möchten, damit der Mangel an deterministischer Zerstörung Sie in Bezug auf Sprachmerkmale nicht in eine Ecke zurückversetzt.

1
GregRos