finalizador não chamado após GC.Collect () [duplicate]

Aug 17 2020

Acho que estou perdendo algo fundamental e espero que você possa ajudar. O código abaixo cria um objeto, remove a referência e chama o coletor de lixo. Minha expectativa era que o finalizador de SomeClass fosse chamado quando estivesse em Readline. Não é verdade. Tentei chamar GC.Collect em um loop, adicionando algumas chamadas Sleep () para que o thread do finalizador fosse iniciado. Não acontece.

Somente quando o Main termina o finalizador é atingido, mas surpreendentemente é atingido duas vezes. O que estou perdendo?

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

Adendo Há uma diferença entre executar um programa no modo de depuração e no modo de liberação. O programa a seguir produz no modo de depuração, Start - Done - Finalizeenquanto no modo de liberação o arquivo de log é exibido Start - Finalize - Done. Este último é o que eu esperava.

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

Respostas

1 V0ldek Aug 16 2020 at 23:33

Objetos que são coletados como lixo se tornam adequados para finalização e são colocados em uma fila de finalização. Não há absolutamente nenhuma garantia de que esses finalizadores serão executados .

Vou redirecioná-lo para as ótimas postagens de Eric Lippert sobre isso, apropriadamente chamadas de "Quando tudo o que você sabe está errado." Em particular:

Mito: definir uma variável como nula faz com que o finalizador seja executado no objeto que foi referenciado anteriormente pela variável. Definir uma variável como nula não faz com que nada aconteça imediatamente, exceto alterar o valor da variável. Se a variável foi a última referência viva ao objeto em questão, então esse fato será descoberto quando o coletor de lixo executar o coletor para qualquer geração em que o objeto estava. (Se for executado, pode não ser. Não há garantia que o GC executa.)

E mesmo com GC.Collect():

Mito: a chamada GC.Collect()faz com que os finalizadores sejam executados. Não, isso faz com que aconteça uma coleta. Isso pode identificar objetos que são candidatos à finalização, mas não força o encadeamento do finalizador a ser agendado. Se é isso que você quer - apenas para fins de teste, por favor! - então ligue GC.WaitForPendingFinalizers().

Portanto, a melhor chance de sucesso é com GC.WaitForPendingFinalizers(). Mas mesmo assim, referindo-se ao resto do post ( e sua sequência ), FINALIZADORES NÃO TÊM GARANTIA DE EXECUÇÃO . Não dependa disso.

Escrevi uma explicação mais detalhada sobre essa questão para que você possa usá-la como ponto de partida para pesquisas futuras.