2013-12-22 3 views
3

Здесь приведен пример программы, которая демонстрирует удивительное финализации поведение:Почему финализации называется на объекте

class Something 
{ 
    public void DoSomething() 
    { 
     Console.WriteLine("Doing something"); 
    } 
    ~Something() 
    { 
     Console.WriteLine("Called finalizer"); 
    } 
} 

namespace TestGC 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var s = new Something(); 
      s.DoSomething(); 
      GC.Collect(); 
      //GC.WaitForPendingFinalizers(); 
      s.DoSomething(); 
      Console.ReadKey(); 
     } 
    } 
} 

Если я запускаю программу, то, что печатается в:

Doing something 
Doing something 
Called finalizer 

Это выглядит, как и ожидалось. Потому что есть ссылка на s после вызова GC.Collect(), s не является мусором.

Теперь удалите комментарии из строки //GC.WaitForPendingFinalizers(); снова и снова создайте программу.

Я бы не ожидал, что на выходе ничего не изменится. Это потому, что я прочитал, что , если объект обнаружен как мусор, и он имеет финализатор, он будет помещен в очередь финализатора. Поскольку объект не является мусором, то представляется логичным, что его нельзя ставить в очередь финализатора. Таким образом, прокомментированная строка ничего не должна делать.

Однако выход программы:

Doing something 
Called finalizer 
Doing something 

Может кто-нибудь помочь моему пониманию того, почему финализации вызывается?

ответ

7

Я не могу воспроизвести проблему на своем ноутбуке, но ваш метод DoSomething не использует никаких полей в объекте. Это означает, что объект может быть завершен , хотя DoSomething работает.

Если вы измените код:

class Something 
{ 
    int x = 10; 

    public void DoSomething() 
    { 
     x++; 
     Console.WriteLine("Doing something"); 
     Console.WriteLine("x = {0}", x); 
    } 
    ~Something() 
    { 
     Console.WriteLine("Called finalizer"); 
    } 
} 

... то я подозреваю, вы всегда см DoingSomething напечатаны дважды «Called финализации» - хотя окончательное «х = 12» могут быть напечатаны после «Вызывается финализатор».

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

+0

Действительно, это имело значение. Благодарю. Пример реальной жизни - это более сложная программа C++/CLI, где мне нужен финализатор. – Tony

+1

@Tony: Насколько вы уверены, что вам действительно нужен финализатор? См. Http://webcache.googleusercontent.com/search?q=cache:pS2g2LI5wMAJ:joeduffyblog.com/2005/12/27/never-write-a-finalizer-again-well-almost-never/+&cd=1&hl= Например, en & ct = clnk & gl = uk. –

3

Ваш DoSomething() настолько ничтожен, что он, скорее всего, станет встроенным. После того, как он был встроен, нет ничего, что все еще имеет ссылку на объект, поэтому нет ничего, что мешало бы ему собирать мусор.

GC.KeepAlive() предназначен специально для этого сценария. Его можно использовать, если вы хотите предотвратить сбор мусора. Он ничего не делает, но сборщик мусора не знает этого. Вызовите GC.KeepAlive(s); в конце Main, чтобы предотвратить его досрочное завершение.

7

Ответ Джона, конечно, правильный. Я бы добавил, что спецификация C# требует, чтобы компилятор и среда выполнения были разрешены (но не )), что ссылка, содержащаяся в локальной переменной, больше не разыменовывается, и в этот момент сборщик мусора разрешено рассматривать объект как мертвый, если местный - это последняя живая ссылка. Поэтому объект может быть собран, и финализатор работает, даже если в живой локальной переменной есть ссылка. (И аналогичным образом, компилятор и среда выполнения разрешают делать жителям дольше, если они того пожелают.)

Учитывая этот факт, вы можете оказаться в причудливых ситуациях. Например, выполнение финализатора может выполняться в потоке финализатора , в то время как конструктор объекта работает на пользовательском потоке. Если среда выполнения может определить, что «это» никогда не разыменовывается снова, тогда объект может считаться мертвым в момент, когда конструктор будет обрабатывать поля «this». Если конструктор затем выполняет дополнительную работу - скажем, изменяя глобальное состояние - тогда эта работа может быть выполнена по завершении финализатора.

Это еще одна причина, по которой писать правильный финализатор настолько сложно, что вы, вероятно, не должны этого делать. В финализаторе everything you know is wrong. Все, о чем вы говорите, может быть мертвым, вы находитесь в другом потоке, объект может быть не полностью построен, возможно, ни один из ваших программных инвариантов фактически не поддерживается.

+0

Чтобы сделать вещи еще хуже, возможно, что объект станет подходящим для немедленной финализации, если нет надежных корневых ссылок на него, но для надежных корневых ссылок, которые нужно восстановить, а для другого кода, чтобы начать использовать объект, перед тем, как финализатор работает. Даже объекты не воскресают сами, очень трудно защитить объект от воскрешения чем-то другим. – supercat

Смежные вопросы