2010-08-17 37 views
5

Consulte el código a continuación. Espero que imprima cualquiera de los 10 porque he invocado explícitamente al recolector de basura. Pero siempre obtengo un 0 o 20 como salida. ¿Porqué es eso?C# Destructor no funciona como se esperaba

void Main() 
{ 
    Panda[] forest_panda = new Panda[10]; 
    for(int i=0; i<forest_panda.GetLength(0);i++) 
    { 
     forest_panda[i]=new Panda("P1"); 
    } 

    for(int i=0; i<forest_panda.GetLength(0);i++) 
    { 
     forest_panda[i]=new Panda("P1"); 
    } 

    System.GC.Collect(); 

    Console.WriteLine("Total Pandas created is {0}",Panda.population);   
} 

class Panda 
{ 
    public static int population=0; 
    public string name; 

    public Panda(string name) 
    { 
     this.name = name; 
     population = population + 1; 
    } 

    ~Panda() 
    { 
     population = population - 1; 
    } 
} 

Tenga en cuenta que la clase principal para el que se crea automáticamente por LINQPad (el editor que viene con la "C# 4.0 en una cáscara de nuez" libro). Soy nuevo en C#.

+1

http://blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx muchos más buenos en la primera página del blog de Raymond ahora mismo. –

+1

¿por qué esperas imprimir 10 cuando creas 20 pandas? –

+0

@Rune FS No esperaba que el segundo conjunto de 10 pandas tuviera GC. pero ahora entiendo que también puede ser GC porque ya no se menciona en el programa cuando llamé a GC. – Manoj

Respuesta

7

No ha ejecutado una recolección de basura explict. A partir de los documentos de GC.Collect():

Utilice este método para intentar reclamar toda la memoria que es inaccesible. Sin embargo, el método Collect no garantiza que se recupere toda la memoria inaccesible .

Todos los objetos, independientemente de cuánto tiempo hayan estado en la memoria , son considerados para recolección; sin embargo, los objetos que se mencionan en el código administrado no se recopilan. Use este método para forzar al sistema a intentar para reclamar la cantidad máxima de memoria disponible .

El colector de garabajes está muy optimizado y "decide" por sí mismo cuando hace la recolección de basura y luego llama a los finalizadores. Además, todo se hace de forma asíncrona. Esa es también la razón por la cual a los finalizadores se les llama limpieza no determinista. Usted nunca ahora cuando ocurre la limpieza.

Tiene dos opciones ahora. Puede llamar al GC.WaitForPendingFinalizers() que detendrá el hilo actual hasta que todos los objetos finalizables hayan sido finalizados. O llame a esta nueva sobrecarga: System.GC.Collect(int generation, System.GCCollectionMode mode) con GCCollectionMode.Forced Se introdujo en .NET 3.5.

Solo tenga en cuenta que generalmente no es necesario y más importante aún: una mala idea llamar al recolector de basura manualmente. También implementar el finalizador solo es necesario en raras ocasiones. Llamar al recolector de basura ralentizará el tiempo de ejecución. La implementación de finalizadores ralentizará el tiempo de ejecución de manera adicional. El recopilador garabge coloca todos los objetos que implementan el finalizador en la cola de finalización cuando están listos para ser recogidos. El procesamiento de esta cola es costoso. Para empeorar las cosas, cuando se ejecuta el finalizador, no está garantizado que los miembros a los que intenta acceder todavía estén vivos. Es muy posible que ya hayan sido grabados. Es por eso que debe utilizar el finalizador solo cuando tenga no administrado recursos que deben ser limpiados.

Todo esto definitivamente no es necesario en su ejemplo. Lo que realmente quiere es IDisposable para la limpieza determinista.

+0

Porque alguna vez el recolector de basura "decidió" que es un buen momento para la recolección de basura y afortunadamente todo el finalizador pendiente ya se había ejecutado. – bitbonk

+0

gracias que funcionó – Manoj

2

Crea veinte objetos, por lo que el valor sería 20. Llamar explícitamente a System.GC.Collect() no garantiza realmente la invocación del destructor. Por lo tanto, si se llamó, todos los 20 objetos pueden haber sido destruidos o ninguno pudo haber sido.

Esto explica lo que está sucediendo realmente.

No es una buena práctica crear un destructor o llamar a GC.Colgar explícitamente.

Si un objeto tiene que hacer la limpieza, se debe implementar IDisposable

6

Hay un par de cosas a destacar aquí:

primer lugar, el GC se comporta de manera diferente entre la liberación y versiones de depuración. En general, en el modo de lanzamiento, los objetos pueden recuperarse antes que en el modo de depuración.

Como Tim señala que llamar a GC.Collect no llama a los finalizadores. Si desea esperar que los finalizadores se ejecuten, llame a GC.WaitForPendingFinalizers también.

Los finalizadores se ejecutan mediante un subproceso dedicado, por lo que en realidad está modificando el estado de dos subprocesos diferentes sin ninguna sincronización. Si bien esto puede no ser un problema en este caso particular, hacerlo no es una buena idea. Pero antes de ir y agregar la sincronización al finalizador, tenga en cuenta que un finalizador estancado significa que no se ejecutarán más finalizadores y, por lo tanto, la memoria de esos objetos no se recuperará.

+0

¡Nunca supe que puede haber problemas de sincronización aquí! ¡Hilos sin que yo cree nada! ¿Es un problema solo cuando uso el finalizador o tengo que cuidarlos cuando uso variables estáticas? – Manoj

+0

El problema en este caso se debe a que los finalizadores se ejecutan mediante un subproceso dedicado creado por el tiempo de ejecución. –

1

En .NET, la duración de los objetos es non-deterministic y no se comporta como cabría esperar de C++ constructor/destructors. De hecho, los objetos .NET técnicamente no tienen destructores. El finalizador difiere en que se espera que limpie recursos no administrados utilizados por el objeto durante su vida útil.

Para tener una forma determinista de liberar recursos utilizados por su objeto, implemente la interfaz IDisposable. IDisposable no es perfecto, ya que todavía requiere el código de llamada para deshacerse del objeto correctamente cuando está hecho, y es difícil manejar múltiples llamadas accidentales a Dispose. Sin embargo, la sintaxis en C# lo hace generalmente muy fácil.

class Panda : IDisposable 
{ 
    public static int population = 0; 
    public string _name; 

    public Panda(string name) 
    { 
     if(name == null) 
      throw new ArgumentNullException(name); 

     _name = name; 
     population++; 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if(disposing && name != null) 
     { 
      population--; 
      name = null; 
     } 
    } 

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

    ~Panda(){ Dispose(false); } 
} 

A continuación, se utiliza la clase:

using(var panda = new Panda("Cute & Cuddly")) 
{ 
    // Do something with the panda 

} // panda.Dispose() called automatically 
+0

Sí, estaba pensando en las líneas del destructor C++ y por eso estaba confundido. Gracias.. – Manoj

1

El uso de destructores (finalizadores alias) no es realmente una buena manera de hacer las cosas en C#. No hay garantía de que el finalizador se ejecute, incluso si invocas explícitamente al recolector de basura. Tampoco debe intentar forzar la recolección de basura, ya que probablemente tendrá un impacto negativo en su aplicación en general.

En su lugar, si necesita liberar explícitamente recursos propiedad de un objeto, debe implementar la interfaz IDisposable y colocar su lógica de limpieza dentro del método Dispose(). Por el contrario, cuando utiliza un objeto que implementa IDisposable, siempre debe tener cuidado de llamar a su método Dispose() cuando haya terminado con él. C# proporciona la declaración de "uso" para este propósito.

Muchas clases que hacen I/O (como Streams) implementan IDisposable. Aquí hay un ejemplo de uso de FileStream para leer un archivo de texto. Tenga en cuenta el "uso" declaración para asegurar la FileStream está dispuesto, cuando hayamos terminado con ella:

using (FileStream fs = File.OpenRead("C:\\temp\\myfile.txt")) 
{ 
    // Read a text file 1024 bytes at a time and write it to the console 
    byte[] b = new byte[1024]; 
    while (fs.Read(b, 0, b.Length) > 0) 
    { 
     Console.WriteLine(Encoding.UTF8.GetString(b)); 
    } 
} // Dispose() is called automatically here 

El código anterior es equivalente a esto:

FileStream fs = File.OpenRead("C:\\temp\\myfile.txt")) 
try 
{ 
    // Read a text file 1024 bytes at a time and write it to the console 
    byte[] b = new byte[1024]; 
    while (fs.Read(b, 0, b.Length) > 0) 
    { 
     Console.WriteLine(Encoding.UTF8.GetString(b)); 
    } 
} 
finally 
{ 
    fs.Dispose(); 
} 
1

El Eliminación del patrón sería la mejor usar. Aquí está la implementación completa de su trabajo.

Recuerde que debe llamar a Dispose por su cuenta como se hace en el código siguiente.

public static void Main() 
    { 
     Panda[] forest_panda = new Panda[10]; 
     for (int i = 0; i < forest_panda.GetLength(0); i++) 
      forest_panda[i] = new Panda("P1"); 

     // Dispose the pandas by your own 
     foreach (var panda in forest_panda) 
      panda.Dispose(); 


     for (int i = 0; i < forest_panda.GetLength(0); i++) 
      forest_panda[i] = new Panda("P1"); 

     // Dispose the pandas by your own 
     foreach (var panda in forest_panda) 
      panda.Dispose(); 

     Console.WriteLine("Total Pandas created is {0}", Panda.population); 
    } 

    class Panda : IDisposable 
    { 
     public static int population = 0; 
     public string name; 

     public Panda(string name) 
     { 
      this.name = name; 
      population = population + 1; 
     } 

     ~Panda() 
     { 
      Dispose(false); 
     } 

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

     private void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       population = population - 1; 
      } 
     } 
    } 
Cuestiones relacionadas