2015-11-10 10 views
22

Ho la seguente classe. Ora a volte l'istruzione lock lancia un ArgumentNullException, e in quel caso posso quindi vedere nel debugger che l'oggetto disposelock è veramente nullo.I campi di sola lettura diventano nulli quando si eliminano dal finalizzatore

Come posso vedere che lo smaltimento è falso, so che il metodo è attivato da Finalizer.

Ma come può accadere? È definito come readonly e ottiene il suo valore quando viene creato l'oggetto.

PS: So che questo non è un buon modello, ma la sua parte di un dato codice, e ho appena non può spiegare il motivo per cui questo diventa nullo

public abstract class DisposableMarshalByRefObject : MarshalByRefObject, IDisposable 
{ 
    private readonly object disposeLock = new object(); 


    /// </summary> 
    ~DisposableMarshalByRefObject() 
    { 
     Dispose(false); 
    } 

    /// <summary> 
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 
    /// </summary> 
    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 

    protected void Dispose(bool disposing) //disposing = false,=> finalizer 
    { 
     lock (disposeLock) //ArgumentNull Exception ! 
     { 
      .... 
     } 
    } 
}   
+3

Mostrare un programma breve ma completo che mostri il problema. Questo mi sembra molto carino. –

+1

C'è un finalizzatore? Forse l'oggetto era già disposto ed è necessario sopprimere la finalizzazione – ehh

+0

Non riesco a riprodurlo in un piccolo programma. Principalmente mi chiedo se possa essere un comportamento atteso che il processo di finalizzazione possa annullare i riferimenti di sola lettura prima che il finalizzatore sia stato completato –

risposta

7

Su garbage collection l'ordine di quella collezione non è definito:

  1. Prima this vengono raccolte
  2. Successivo disposeLock viene raccolto

O

  1. Prima disposeLock vengono raccolte
  2. successivo this viene raccolto

Quindi non utilizzare alcun riferimento campi (struct come int, bool ecc sono sicuri) il Dispose(false);

protected virtual void Dispose(bool disposing) { 
    if (disposing) { 
    // Explicit disposing: it's safe to use disposeLock 
    lock (disposeLock) { 
     ... 
    } 
    } 
    else { 
    // Garbage collection: there's no guarantee that disposeLock has not been collected 
    } 
} 
+2

Sei sicuro? AFAIK con un finalizzatore non ancora attivato fa resuscitare l'oggetto e tutti gli oggetti a cui fa riferimento. Ciò che non è definito è l'ordine in cui vengono chiamati i finalizzatori. Mentre l'ordine di raccolta non è specificato, nessuno dei due oggetti può essere raccolto fino a quando non è stato eseguito il finalizzatore e si può osservare questo ordine non definito tramite riferimenti deboli, che non sono mostrati nell'esempio. – CodesInChaos

+0

@CodesInChaos: 'disposeLock' non ha finalizzatore, non può essere resuscitato ed è per questo che può essere raccolto –

+2

AFAIK un oggetto con un finalizzatore impedisce temporaneamente la raccolta (ma non la finalizzazione) di tutti gli oggetti raggiungibili da esso. Se resuscita permanentemente se stesso, quegli oggetti referenziati vengono risuscitati permanentemente. – CodesInChaos

4

Poiché hai accentuato readonly, un piccolo chiarimento a riguardo. Il runtime non impedisce la modifica dei campi readonly. Indipendentemente da readonly da C# diventa initonly in IL.

Ad esempio, è possibile modificare facilmente readonly campo utilizzando la riflessione:

class A 
{ 
    private readonly int bar; 

    public A() 
    { 
     bar = 1; 
    } 

    public void Foo() 
    { 
     Console.WriteLine(bar); 
    } 
} 

var a = new A(); 

a.Foo(); // displays "1" 
a.GetType().GetField("bar", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(a, 2); 
a.Foo(); // displays "2" 

Naturalmente, questo non significa che si dovrebbe provare questi campi per null ogni volta, ma ci potrebbe essere casi, quando il campo readonly viene modificato (hai affrontato uno di essi).

Come nota a margine. Hai davvero bisogno di finalizzatore? Voglio dire, ci sono delle risorse non gestite true?

5

Tutte le risposte esistenti tranne la risposta di riflessione sono false. Il GC non imposta riferimenti a null quando raccoglie oggetti. L'accesso agli oggetti non fallisce spurie a causa del GC. L'ordine di finalizzazione non è definito ma tutti i riferimenti a oggetti esistenti continuano a esistere e sono validi.

La mia ipotesi per quello che è successo: il costruttore è stato interrotto prima che il campo fosse inizializzato. Quello ha lasciato il campo null. Il finalizzatore poi lo ha trovato in quel modo.

Un costruttore può essere interrotto generando un'eccezione o chiamando Thread.Abort che è male.

Sulla raccolta dei rifiuti l'ordine di quella collezione non è definito

L'ordine di collezione non è osservabile (se non attraverso riferimenti deboli ...). L'ordine di finalizzazione è osservabile, ma non con l'istruzione lock perché gli oggetti non perdono la possibilità di sincronizzarsi quando finalizzati.

+2

Il (dis) dimostrare questa spiegazione, inserire una chiamata di registrazione nel costruttore (che mostra che l'oggetto è stato costruito completamente) e se si ottiene un finalizzatore che genera l'eccezione di riferimento null, controllare i registri per vedere se il costruttore per esso finshed, o no. – CodesInChaos

Problemi correlati