Quando utilizzare i riferimenti deboli in .Net?

51

Non mi sono mai imbattuto in una situazione in cui avevo bisogno di usare il tipo WeakReference in .Net, ma la credenza popolare sembra essere che dovrebbe essere usato in cache. Il dott. Jon Harrop ha fornito un ottimo esempio contro l'utilizzo di WeakReferences in cache nella sua risposta a questa domanda .

Ho anche sentito spesso che gli sviluppatori AS3 parlano dell'utilizzo di riferimenti deboli per risparmiare sull'orma della memoria, ma in base alle conversazioni che ho avuto sembra aggiungere complessità senza necessariamente raggiungere l'obiettivo prefissato e il comportamento di runtime è piuttosto imprevedibile. Tanto che molti semplicemente rinunciano a questo e invece gestiscono l'utilizzo della memoria con più attenzione / ottimizzano il loro codice per risparmiare meno memoria (o fare il trade-off di più cicli della CPU e un minore ingombro della memoria).

Il dott. Jon Harrop ha anche sottolineato nella sua risposta che i riferimenti deboli .Net non sono soft, e c'è una collezione aggressiva di riferimenti deboli su gen0. Secondo MSDN , i lunghi riferimenti deboli ti danno la possibilità di ricreare un oggetto, but the state of the object remains unpredictable. !

Viste queste caratteristiche, non riesco a pensare a una situazione in cui i riferimenti deboli sarebbero utili, forse qualcuno potrebbe illuminarmi?

    
posta theburningmonk 31.01.2013 - 00:40
fonte

6 risposte

35

Ho trovato applicazioni pratiche legittime di riferimenti deboli nei seguenti tre scenari del mondo reale che mi sono realmente accaduti personalmente:

Applicazione 1: gestori di eventi

Sei un imprenditore. La tua azienda vende un controllo delle linee di scintilla per WPF. Le vendite sono grandi ma i costi di supporto ti stanno uccidendo. Troppi clienti si lamentano di CPU hogging e perdite di memoria quando scorrono attraverso schermi pieni di linee di scintilla. Il problema è che la loro app sta creando nuove linee di scintilla non appena vengono visualizzate, ma l'associazione dei dati impedisce di raccogliere i vecchi. Che cosa fai?

Introducete un riferimento debole tra l'associazione dei dati e il vostro controllo in modo che l'associazione dei dati da sola non impedisca più che il vostro controllo venga sottoposto a garbage collection. Quindi aggiungi un finalizzatore al tuo controllo che abbatte l'associazione dati quando viene raccolto.

Applicazione 2: grafici mutevoli

Sei il prossimo John Carmack. Hai inventato un'ingegnosa nuova rappresentazione basata su grafici di superfici di suddivisione gerarchica che rende i giochi di Tim Sweeney come una console Nintendo Wii. Ovviamente non ho intenzione di dirti esattamente come funziona ma è tutto incentrato su questo grafico mutevole in cui i vicini di un vertice può essere trovato in un Dictionary<Vertex, SortedSet<Vertex>> . La topologia del grafico continua a cambiare mentre il giocatore corre in giro. C'è solo un problema: la struttura dei dati sta perdendo sottografi irraggiungibili mentre è in esecuzione e devi rimuoverli o perderai memoria. Per fortuna sei un genio, quindi sai che esiste una classe di algoritmi progettati specificamente per localizzare e collezionare sottografi irraggiungibili: i garbage collector! Leggi l'eccellente monografia di Richard Jones sull'argomento ma ti lascia perplesso e preoccupato per la tua imminente scadenza. Che cosa fai?

Semplicemente sostituendo il Dictionary con una tabella hash debole puoi piggyback del GC esistente e raccogliere automaticamente i tuoi sottografi irraggiungibili! Torna a sfogliare le pubblicità Ferrari.

Applicazione 3: Decorazione alberi

Sei appeso al soffitto di una stanza cilindrica a una tastiera. Hai 60 secondi per setacciare alcuni BIG DATA prima che qualcuno ti trovi. Siete venuti preparati con un parser bellissimo basato sul flusso che si basa sul GC per raccogliere frammenti di AST dopo che sono stati analizzati. Ma ti rendi conto che hai bisogno di metadati aggiuntivi su ogni% AST diNode e ne hai bisogno in fretta. Che cosa fai?

Potresti usare un Dictionary<Node, Metadata> per associare i metadati a ciascun nodo ma, a meno che non lo elimini, i forti riferimenti dal dizionario ai vecchi nodi AST li manterranno vivi e perdono memoria. La soluzione è una tabella hash debole che mantiene solo i riferimenti deboli alle chiavi e garbage collects i binding di valori-chiave quando la chiave diventa irraggiungibile. Quindi, man mano che i nodi AST diventano irraggiungibili, vengono eliminati dalla garbage collection e il loro binding di valori-chiave viene rimosso dal dizionario, lasciando i metadati corrispondenti irraggiungibili così da essere raccolti. Quindi tutto quello che devi fare dopo che il tuo ciclo principale è terminato è scivolare di nuovo attraverso la presa d'aria ricordando di sostituirlo proprio mentre entra la guardia di sicurezza.

Si noti che in tutte e tre queste applicazioni del mondo reale che mi sono realmente accadute, volevo il GC per raccogliere il più aggressivamente possibile. Ecco perché queste sono applicazioni legittime. Tutti gli altri hanno torto.

    
risposta data 08.02.2013 - 02:10
fonte
18

Given these characteristics, I can't think of a situation where weak references would be useful, perhaps someone could enlighten me?

Documento Microsoft Modelli di eventi deboli .

In applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. This situation can lead to memory leaks. Windows Presentation Foundation (WPF) introduces a design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. This design pattern is known as the weak event pattern.

...

The weak event pattern is designed to solve this memory leak problem. The weak event pattern can be used whenever a listener needs to register for an event, but the listener does not explicitly know when to unregister. The weak event pattern can also be used whenever the object lifetime of the source exceeds the useful object lifetime of the listener. (In this case, useful is determined by you.) The weak event pattern allows the listener to register for and receive the event without affecting the object lifetime characteristics of the listener in any way. In effect, the implied reference from the source does not determine whether the listener is eligible for garbage collection. The reference is a weak reference, thus the naming of the weak event pattern and the related APIs. The listener can be garbage collected or otherwise destroyed, and the source can continue without retaining noncollectible handler references to a now destroyed object.

    
risposta data 31.01.2013 - 01:06
fonte
12

Lascia che lo metta per primo e ritorni ad esso:

A WeakReference is useful when you want to keep tabs on an object, but you DO NOT want your observations to prevent that object from being collected

Quindi iniziamo dall'inizio:

- mi scuso in anticipo per qualsiasi offesa involontaria, ma tornerò al livello "Dick and Jane" per un momento poiché non si può mai dire al proprio pubblico.

Quindi quando hai un oggetto X - specifichiamolo come un'istanza di class Foo - NON PUO 'viverlo da solo (per lo più vero); Nello stesso modo in cui "Nessun uomo è un'isola", ci sono solo alcuni modi in cui un oggetto può essere promosso a Islandhood - sebbene si chiami essere una radice di GC nel linguaggio CLR. Essendo una radice di GC, o avendo una catena stabilita di connessioni / riferimenti a una radice di GC, è fondamentalmente ciò che determina se Foo x = new Foo() ottiene o meno la garbage collection.

Se non riesci a risalire verso la radice di un GC per heap o stack walking, sei effettivamente orfano e probabilmente verrà contrassegnato / raccolto il prossimo ciclo.

A questo punto, esaminiamo alcuni esempi orribili:

Innanzitutto, il nostro 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--);
    }
}

Semplicemente semplice - non è thread-safe, quindi non provarlo, ma mantiene un "conteggio dei riferimenti" approssimativo delle istanze attive e dei decrementi quando vengono finalizzati.

Ora diamo un'occhiata a FooConsumer :

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");
    }
}

Quindi abbiamo un oggetto che è già una root GC di sua proprietà (beh ... per essere precisi, verrà radicata tramite una catena direttamente al dominio dell'app che esegue questa applicazione, ma questo è un altro argomento) ha due metodi di aggancio su un'istanza Foo : testiamolo:

// 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();

Ora, da quanto sopra, ti aspetteresti che l'oggetto-che-è-stato-riferito-a-da f sia "collezionabile"?

No, perché ora c'è un altro oggetto con un riferimento ad esso - il Dictionary in quell'istanza di Singleton static.

Ok, proviamo l'approccio debole:

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();

Ora, quando eseguiamo il nostro riferimento a %-Foo -that-was-once- f , non ci sono più riferimenti "duri" all'oggetto, quindi è collettabile - il WeakReference creato da l'ascoltatore debole non lo impedirà.

Buoni casi d'uso:

  • Gestori di eventi (Anche se leggi prima questo: Eventi deboli in C # )

  • Hai una situazione in cui potresti causare un "riferimento ricorsivo" (ad esempio, l'oggetto A si riferisce all'oggetto B, che si riferisce all'oggetto A, indicato anche come "Perdita di memoria") (modifica: derp, ovviamente questo non è vero)

  • Vuoi "trasmettere" qualcosa a una collezione di oggetti, ma non vuoi essere la cosa che li mantiene in vita; un List<WeakReference> può essere mantenuto facilmente, e persino potato eliminando dove ref.Target == null

risposta data 31.01.2013 - 01:40
fonte
3

Come logiche perdite che sono davvero difficili da rintracciare mentre gli utenti tendono a notare che l'esecuzione del software per un lungo periodo tende a prendere sempre più memoria e rallentare e rallentare fino al riavvio? Io no.

Considera cosa succede se, quando l'utente richiede di rimuovere la risorsa dell'applicazione sopra, Thing2 non riesce a gestire correttamente un evento del genere sotto:

  1. Puntatori
  2. Riferimenti forti
  3. Riferimenti deboli

... e in base al quale uno di questi errori sarebbe stato probabilmente catturato durante i test, e quale non sarebbe stato e sarebbe volato sotto il radar come un insetto stealth combattente. La proprietà condivisa è, più spesso della maggior parte, un'idea priva di senso.

    
risposta data 04.01.2016 - 19:25
fonte
1

Un esempio molto illustrativo di riferimenti deboli usati con buoni risultati è ConditionalWeakTable , che viene utilizzato dal DLR (tra gli altri luoghi) per collegare ulteriori "membri" agli oggetti.

Non vuoi che il tavolo mantenga vivo l'oggetto. Questo concetto semplicemente non poteva funzionare senza riferimenti deboli.

Ma mi sembra che tutti gli usi per i riferimenti deboli siano arrivati molto tempo dopo che sono stati aggiunti al linguaggio, poiché i riferimenti deboli hanno fatto parte di .NET dalla versione 1.1. Sembra proprio qualcosa che vorresti aggiungere, in modo che la mancanza di distruzione deterministica non ti tiri indietro in un angolo per quanto riguarda le caratteristiche linguistiche.

    
risposta data 04.01.2016 - 20:13
fonte
-2

Se il livello cache è implementato con C #, è molto meglio mettere i dati nella cache come riferimenti deboli, potrebbe aiutare a migliorare le prestazioni del livello cache.

Pensa che questo approccio potrebbe essere applicato anche all'implementazione della sessione. Poiché la sessione è oggetto di lunga vita per la maggior parte del tempo, potrebbe essere un caso in cui non si ha memoria per il nuovo utente. In tal caso sarà molto meglio cancellare qualche altro oggetto sessione utente e poi lanciare OutOfMemoryException.

Inoltre, se hai un oggetto di grandi dimensioni nella tua applicazione (una grande tabella di ricerca, ecc.), dovrebbe essere usato abbastanza di rado e ricreare un oggetto di questo tipo non è una procedura molto costosa. Quindi è meglio avere come riferimento una settimana per avere un modo per liberare la memoria quando ne hai davvero bisogno.

    
risposta data 31.01.2013 - 01:01
fonte

Leggi altre domande sui tag