Может кто-нибудь объяснить это поведение завершения - PullRequest
6 голосов
/ 04 ноября 2011

Пока «расследовал» финализацию (читай: пробовал глупости), я наткнулся на какое-то неожиданное поведение (по крайней мере для меня).

Я бы ожидал, что метод Finalize не будет вызываться, тогда как он вызывается дважды

class Program
{
    static void Main(string[] args)
    {
        // The MyClass type has a Finalize method defined for it
        // Creating a MyClass places a reference to obj on the finalization table.
        var myClass = new MyClass();

        // Append another 2 references for myClass onto the finalization table.
        System.GC.ReRegisterForFinalize(myClass);
        System.GC.ReRegisterForFinalize(myClass);
        // There are now 3 references to myClass on the finalization table.

        System.GC.SuppressFinalize(myClass);
        System.GC.SuppressFinalize(myClass);
        System.GC.SuppressFinalize(myClass);

        // Remove the reference to the object.
        myClass = null;

        // Force the GC to collect the object.
        System.GC.Collect(2, System.GCCollectionMode.Forced);

        // The first call to obj's Finalize method will be discarded but
        // two calls to Finalize are still performed.
        System.Console.ReadLine();
    }
}

class MyClass
{
    ~MyClass()
    {
        System.Console.WriteLine("Finalise() called");
    }
}

Может ли кто-нибудь объяснить, является ли это поведение преднамеренным, и если да, то почему?

Этот код был скомпилирован в режиме отладки x86 и запущен на CLR v4.

Большое спасибо

Ответы [ 4 ]

16 голосов
/ 04 ноября 2011

Я не знаю, что вызывает странное поведение.Однако, поскольку вы нарушаете документированное использование метода, может произойти все что угодно.Документация для ReRegisterForFinalize гласит:

Запрашивает системный вызов финализатора для указанного объекта , для которого ранее вызывался SuppressFinalize.

Youранее не вызывал SuppressFinalize до того, как вы вызвали ReRegisterForFinalize.В документации не говорится, что происходит в такой ситуации, и на самом деле происходит нечто действительно странное.

К сожалению, на той же странице документации затем показан пример, в котором ReRegisterForFinalize вызывается для объекта, для которого SuppressFinalize не был вызван.

Это немного беспорядок.Я расскажу об этом вместе с менеджером документации.

Мораль этой истории, конечно, в том, что если больно, когда вы нарушаете правила, описанные в документации, тогда перестанете их нарушать .

10 голосов
/ 04 ноября 2011

Я могу угадать ... и это действительно - это только предположение.Как говорит Эрик, не нарушайте правила, подобные этим :) Это предположение только ради пустых предположений и интереса.

Я подозреваю, что задействованы две структуры данных:

  • Очередь финализации
  • Заголовок объекта

Когда GC замечает, что объект подходит для сборки мусора, я подозреваю, что он проверяет заголовок объекта и добавляет ссылку на финализациюочередь.Ваши вызовы SuppressFinalization предотвращают такое поведение.

Отдельно поток финализатора проходит через очередь финализации и вызывает финализатор для всего, что он находит.Ваши звонки на ReRegisterForFinalize обходят нормальный способ, которым ссылка попадает в очередь, и добавляют ее напрямую.SuppressFinalization не удаляет ссылку из очереди - она ​​только останавливает добавление ссылки в очередь обычным способом *1021*.

Все это объясняет поведение, которое вы 'видишь (и который я воспроизвел).Это также объясняет, почему, когда я удаляю вызовы SuppressFinalization, я в конечном итоге вижу финализатор, который вызывается три раза - потому что в этом случае "нормальный" путь добавляет ссылку на очередь финализации.

5 голосов
/ 04 ноября 2011

Интересные точки данных:

  • mono 2.10.8.1 в Linux не вызывает финализатор
  • mono 2.8 в Linux не вызывает финализатор: http://ideone.com/J6pl4
  • mono 2.8.1 на Win32 не вызывает финализатор
  • mono 2.6.7 на Win32 не вызывает финализатор

  • .NET 3.5 на Win32 дважды вызывает финализатор

Тестовый код для справки:

class Program
{
    static void Main(string[] args)
    {
        // The MyClass type has a Finalize method defined for it
        // Creating a MyClass places a reference to obj on the finalization table.
        var myClass = new MyClass();

        // Append another 2 references for myClass onto the finalization table.
        System.GC.ReRegisterForFinalize(myClass);
        System.GC.ReRegisterForFinalize(myClass);
        // There are now 3 references to myClass on the finalization table.

        System.GC.SuppressFinalize(myClass);
        System.GC.SuppressFinalize(myClass);
        System.GC.SuppressFinalize(myClass);

        // Remove the reference to the object.
        myClass = null;

        // Force the GC to collect the object.
        System.GC.Collect(2, System.GCCollectionMode.Forced);

        // The first call to obj's Finalize method will be discarded but
        // two calls to Finalize are still performed.
    }
}

class MyClass
{
    ~MyClass()
    {
        System.Console.WriteLine("Finalise() called");
    }
}
2 голосов
/ 04 ноября 2011

Я подозреваю, что это относится к сфере "неопределенного поведения". Если вы посмотрите на документацию для ReRegisterForFinalize и SuppressFinalize, они скажут:

Параметр obj должен вызывать этот метод.

И это не относится к вашему коду.

...