Pattern per rilevare quando tutti i riferimenti tranne uno (in un linguaggio gestito) vengono distrutti?

4

Nella mia applicazione ho una classe, e ogni istanza di questa classe condivide parte di una risorsa non gestita. La condivisione è facilitata da un oggetto gestore.

Quando un'istanza viene distrutta, il gestore deve essere informato in modo che possa riutilizzare le risorse allocate.

Mentre potrei rendere esplicitamente il programmatore responsabile della distruzione dell'istanza, mi piacerebbe seguire il più possibile il modello del linguaggio gestito. Cioè, mi piacerebbe lasciarlo al runtime per determinare quando un oggetto è idoneo per la distruzione e informare automaticamente il gestore.

Il problema è che il gestore ha bisogno di un elenco di tutte le istanze per mantenere le proprie risorse.

Come posso mantenere un riferimento a un oggetto gestito, pur continuando a consentirne la distruzione automatica quando tutti gli altri riferimenti vengono eliminati?

Per complicarlo ulteriormente, questa particolare applicazione è molto sensibile alle prestazioni.

    
posta sebf 24.01.2017 - 19:14
fonte

5 risposte

10

Un approccio semplice consiste nel far controllare il tuo manager con oggetti deboli. I riferimenti deboli consentono di mantenere un riferimento a un oggetto, ma il riferimento debole non impedirà che vengano raccolti.

var reference = new WeakReference<SomeType>(myObjectToManage);

// later
SomeType value;
if (reference.TryGetTarget(out value)) {
    // the object referenced is still alive.
}
else {
    // the object referenced has been garbage collected.
}

L'unico svantaggio di questo approccio è che gli oggetti possono sopravvivere per un po 'dopo che non ci sono riferimenti a loro, fino a quando non avviene un garbage collection.

Un'altra opzione sarebbe quella di implementare il proprio schema di conteggio dei riferimenti. Ciò richiede una certa disciplina nel codice client che utilizza questi oggetti per assicurarsi che i riferimenti siano tracciati correttamente.

    
risposta data 24.01.2017 - 19:30
fonte
4

Questa è una domanda piuttosto ampia, ulteriormente complicata dalla tua non molto ben definita sensibilità delle prestazioni. Tuttavia, fondamentalmente, hai alcune scelte:

Chiedi al gestore di agire come fabbrica per distribuire i wrapper. I wrapper vengono quindi raccolti come oggetti normali (tramite GC). I wrapper implementano un finalizzatore che informa il manager che la risorsa avvolta sottostante viene rilasciata. In questo modo il gestore può contare (alla consegna dei wrapper) e down (sulla versione di wrapper finalizer) per rilasciare le risorse sottostanti.

Questo tipo di conteggio semplice presuppone che le risorse siano esterne o almeno acicliche.

Se i cicli tra le risorse sono possibili, è necessario utilizzare una gestione aggiuntiva, che potrebbe essere la scansione periodica dei riferimenti (ad esempio il proprio mini-GC).

Utilizza Acquisizione risorse è inizializzazione , modello, come adattato a C # modella l'istruzione using e IDisposable . Questo spesso si traduce anche in wrapper. Vedi link

Si noti che in C # è possibile utilizzare le strutture per i wrapper leggeri; tuttavia, non se ti affidi ai finalizzatori.

E come le altre note di @Erik, possiamo usare C # built-in classi di riferimento deboli . Da MSDN:

A weak reference allows the garbage collector to collect an object while still allowing an application to access the object. If you need the object, you can still obtain a strong reference to it and prevent it from being collected. For more information about how to use short and long weak references, see Weak References.

    
risposta data 24.01.2017 - 19:38
fonte
1

Non esiste un modo semplice per i linguaggi raccolti da garbage come Java o C #. Eliminando la gestione della memoria deterministica / RAII, hanno reso più complicata la gestione delle risorse se la tua risorsa non è memoria e se vuoi incapsulare il più possibile la gestione delle risorse.

Per ridurre la necessità di gestione manuale delle risorse, C # ha blocchi using () , Java ha provato con risorse. Puoi forse introdurre un oggetto che rappresenta un handle per l'oggetto reale. Quando l'handle viene chiuso, la proprietà dell'oggetto torna al pool. Creando l'handle in un blocco using, non è necessario rilasciare la proprietà manualmente, poiché la lingua si occupa di ciò. Per una proprietà condivisa complessa, questo non è sufficiente e dovrai ricorrere alla gestione interamente manuale delle risorse.

Il tentativo di hackerare il sistema GC con tecniche come il modello a peso mosca spesso non vale la pena. L'indizio indiretto, la contabilità e la possibile spazzatura da oggetti temporanei associati a tale soluzione potrebbero superare i vantaggi di un pool di oggetti. Assicurati di misurare che è meglio, invece di risolvere anticipatamente una soluzione.

    
risposta data 24.01.2017 - 19:42
fonte
0

Dai un'occhiata a questo e questo sembra che tu debba implementare IDisposable nelle tue classi per notificare all'oggetto gestore ogni volta che una delle istanze viene distrutta, dovresti anche implementare il tuo sistema di conteggio dei riferimenti nel gestore, dovrebbe fare una semplice fabbrica.

Quindi le tue classi gestite saranno qualcosa del tipo:

   public class SharedResourceWrapper: IDisposable {

    private Manager manager; // a handle to the manager

    public SharedResourceWrapper(Manager manager){
        this.manager = ... // allocates the resource
    }

    public void Dispose(){
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing){
        if (disposing) {
            manager.RemoveReference();
        }
    }
}

E la tua classe Manager dovrebbe avere questi due metodi

public SharedResourceWrapper Create(){
  var newInstance = new SharedResourceWrapper(this)
   references++;
}

 public void RemoveReference() {
    ...
    reference--;
    if( reference == 0){
     ....
    }
   }
    
risposta data 24.01.2017 - 19:38
fonte
0

Disponi modello

Quando una classe controlla la durata delle risorse non gestite, devi almeno implementare IDisposable . Ciò consente ai chiamanti di rilasciare in modo deterministico la risorsa non gestita chiamando sia Dispose che con un'istruzione using :

using (var unmanaged = GetUnmanagedThing())
{
    // Doing stuff with unmanaged
}

L'implementazione di IDisposable è relativamente semplice. Nel tuo caso ogni istanza dovrebbe essere costruita con un riferimento al gestore e dovrebbe chiamare il manager quando sono eliminati. Assicurati di seguire queste linee guida:

  • Dispose può essere chiamato più volte per un singolo oggetto, assicurati che il tuo metodo Dispose lo gestisca.
  • Non lancia eccezioni da Dispose a meno che l'intero processo non vada a fuoco.
  • Assicurati di interrompere l'utilizzo dei metodi sull'oggetto quando è stato eliminato, in genere lanciando ObjectDisposedException .

Il articolo sui modelli di smaltimento MSDN entra molto di più dettaglio. Questa pagina ti consiglia di implementare la logica di smaltimento in un metodo virtuale Dispose(bool disposing) in quanto consente ai finalizzatori e alle classi derivate di gestire correttamente lo smaltimento degli oggetti. Se il tuo oggetto non ha ereditari e non usa la finalizzazione, questo pattern non è necessario e puoi semplicemente implementare direttamente il metodo Dispose .

La finalizzazione

Garantire che le risorse non gestite vengano distrutte quando un oggetto viene sottoposto a Garbage Collection (senza essere stato eliminato per primo) coinvolge i finalizzatori, che sono notoriamente difficili da implementare correttamente perché tutto ciò che sai è sbagliato . In particolare, gli oggetti possono essere disponibili per la raccolta prima di quanto si pensi, ad esempio un oggetto può diventare idoneo per la raccolta durante l'esecuzione di un metodo su quello stesso oggetto . Occorre quindi prestare attenzione per garantire che le risorse gestite non vengano smaltite mentre sono ancora in uso.

I finalizzatori vengono eseguiti solo dopo che un oggetto è stato sottoposto a garbage collection, che non è deterministico. Potrebbe esserci un lungo ritardo tra il momento in cui un oggetto non viene più referenziato e quando viene finalizzato, il che significa un lungo ritardo prima che le risorse non gestite vengano distrutte. Inoltre i finalizzatori comportano una penalizzazione delle prestazioni poiché il finalizzatore deve essere programmato per essere eseguito e la memoria per l'oggetto non può essere recuperata fino al termine dell'esecuzione del finalizzatore.

Se la tua applicazione è sensibile alle prestazioni, dovresti cercare di smaltire le risorse non gestite utilizzando il modello Dispose, il che rende la finalizzazione inutile. Pertanto il mio consiglio sarebbe di evitare di basarsi sulla finalizzazione e invece di garantire che tutte le parti della propria applicazione utilizzino correttamente il modello di eliminazione . Puoi aggiungere la registrazione al tuo finalizzatore per assicurarti che il pattern di smaltimento non venga seguito correttamente:

public void Dispose() {
    // Implement dispose here....
    GC.SuppressFinalize(this);
}

~UnmanagedThing() {
    Log.Warning("Unmanaged thing not properly disposed");
}

Se questa soluzione non è adatta, potresti essere in grado di utilizzare un SafeHandle per smaltire le risorse non gestite.

No davvero, voglio scrivere un finalizzatore

L'idea generale è di garantire che il tuo oggetto sia irraggiungibile, ad es. facendo in modo che il tuo manager abbia solo un riferimento debole al tuo oggetto, o che il tuo manager abbia un riferimento a un oggetto nidificato. Se il tuo oggetto diventa idoneo per la distruzione, il finalizzatore (si spera) verrà chiamato e potrà essere utilizzato per rilasciare le tue risorse non gestite.

    
risposta data 24.01.2017 - 23:48
fonte

Leggi altre domande sui tag