no se llama al finalizador después de GC.Collect () [duplicar]

Aug 17 2020

Creo que me falta algo fundamental y espero que puedan ayudar. El siguiente código crea un objeto, elimina la referencia y llama al recolector de basura. Mi expectativa era que se llamaría al finalizador de SomeClass cuando estuviera en Readline. No es así. Intenté llamar a GC.Collect en un bucle, agregando algunas llamadas a Sleep () para que se iniciara el hilo del finalizador. No sucede.

Solo cuando el Main termina, el finalizador recibe un golpe, pero sorprendentemente recibe dos golpes. ¿Qué me estoy perdiendo?

class Program
{
    public static void Main(string[] args)
    {
        SomeClass some = new SomeClass("Hello World!");
        some = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Done");
        Console.ReadLine();
    }
}

class SomeClass 
{
    string ss;
    public SomeClass(string s) { ss = s; }
    ~SomeClass()
    {
        var hash = this.GetHashCode();
    }
}

Apéndice Existe una diferencia entre ejecutar un programa en modo de depuración y en modo de liberación. El siguiente programa produce en modo de depuración Start - Done - Finalizemientras que en el modo de lanzamiento muestra el archivo de registro Start - Finalize - Done. Esto último es lo que esperaba.

class Program
{
    private static string logfile = @"c:\temp\log.txt";
    public static void Main(string[] args)
    {
        File.WriteAllText(logfile, "Start\n");
        SomeClass some = new SomeClass("Hello World!");
        some = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        File.AppendAllText(logfile, "Done\n");
    }
}

class SomeClass 
{
    private static string logfile = @"c:\temp\log.txt";
    public string SomeString { get; set; }
    public SomeClass(string s) { SomeString = s; }
    ~SomeClass()
    {
        File.AppendAllText(logfile, "Finalize\n");
    }
}

Respuestas

1 V0ldek Aug 16 2020 at 23:33

Los objetos que se recolectan como basura se vuelven aptos para la finalización y se colocan en una cola de finalización. No hay absolutamente ninguna garantía de que esos finalizadores vayan a ejecutarse alguna vez .

Los voy a redirigir a las excelentes publicaciones de Eric Lippert sobre esto, apropiadamente llamadas "Cuando todo lo que sabes está mal". En particular:

Mito: establecer una variable en nula hace que el finalizador se ejecute en el objeto al que la variable hacía referencia anteriormente. Establecer una variable en nulo no hace que suceda nada de inmediato, excepto cambiar el valor de la variable. Si la variable fue la última referencia viva al objeto en cuestión, ese hecho se descubrirá cuando el recolector de basura ejecute el recolector para cualquier generación en la que se encuentre el objeto. (Si se ejecuta, es posible que no lo haga. No hay garantía que funciona el GC.)

E incluso con GC.Collect():

Mito: Llamar GC.Collect()hace que los finalizadores se ejecuten. No, provoca una colección. Eso puede identificar objetos que son candidatos para la finalización, pero no obliga a programar el subproceso del finalizador. Si eso es lo que desea, ¡solo con fines de prueba, por favor! - entonces llama GC.WaitForPendingFinalizers().

Entonces, la mejor posibilidad de éxito es con GC.WaitForPendingFinalizers(). Pero incluso entonces, refiriéndote al resto de la publicación ( y su secuela ), NO SE GARANTIZA QUE LOS FINALIZADORES SE EJECUTEN . No dependas de eso.

Escribí una explicación más detallada sobre esta pregunta para que pueda usarla como punto de partida para futuras investigaciones.