GC.Collect()の後にファイナライザーが呼び出されない[重複]

Aug 17 2020

私は基本的な何かが欠けていると思います、そしてあなたが助けてくれることを願っています。以下のコードは、オブジェクトを作成し、参照を削除して、ガベージコレクターを呼び出します。私の期待は、Readlineに立っているときにSomeClassのファイナライザーが呼び出されることでした。そうではありません。ループでGC.Collectを呼び出して、Sleep()呼び出しを追加して、ファイナライザースレッドを開始してみました。起こりません。

メインが終了したときにのみファイナライザーがヒットしますが、驚くべきことに2回ヒットします。何が足りないのですか?

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

補遺デバッグモードとリリースモードでのプログラムの実行には違いがあります。以下のプログラムはデバッグモードで生成されますStart - Done - Finalizeが、リリースモードではログファイルにが表示されますStart - Finalize - Done。後者は私が期待したものです。

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

回答

1 V0ldek Aug 16 2020 at 23:33

ガベージコレクションされたオブジェクトはファイナライズに適しており、ファイナライズキューに入れられます。これらのファイナライザーが実行されるという保証はまったくありません

これに関するEricLippertからのすばらしい投稿にリダイレクトします。これは、適切に「あなたが知っているすべてが間違っているとき」と呼ばれます。特に:

神話:変数をnullに設定すると、以前に変数によって参照されていたオブジェクトでファイナライザーが実行されます。変数をnullに設定しても、変数の値を変更する以外は、すぐには何も起こりません。変数が問題のオブジェクトへの最後の生きた参照であった場合、その事実は、ガベージコレクターがオブジェクトの世代に関係なくコレクターを実行したときに発見されます(実行されたとしても、実行されない可能性があります。保証はありません。 GCが実行されること。)

そしてとさえGC.Collect()

神話:呼び出すGC.Collect()とファイナライザーが実行されます。いいえ、コレクションが発生します。これにより、ファイナライズの候補となるオブジェクトが識別される可能性がありますが、ファイナライザースレッドを強制的にスケジュールすることはありません。それが必要な場合—テスト目的でのみお願いします!—次にを呼び出しますGC.WaitForPendingFinalizers()

したがって、成功の可能性が最も高いのはGC.WaitForPendingFinalizers()。しかし、それでも、投稿の残りの部分(およびその続編)を参照すると、ファイナライザーの実行は保証されません。それに依存しないでください。

この質問についてより詳細な説明を書いたので、それをさらなる研究の出発点として使用できます。