2015-08-03 2 views
4

В StackOverflow есть несколько обсуждений о том, что делать, если мой объект управляет другими управляемыми объектами, которые реализуют System.IDisposable.Устранение моего объекта System.IDisposable в моем финализаторе

Примечание: Ниже я не говорить о неуправляемого код. Я полностью понимаю важность очистки неуправляемого кода

Большинство дискуссий говорят, что если объект принадлежит другой управляемый объект, который реализует System.IDisposable, то вы должны также осуществлять System.IDisposable, в этом случае вы должны позвонить в Dispose() из одноразовых предметов ваш объект держится. Это было бы логично, потому что вы не знаете, использует ли ваш одноразовый объект неуправляемый код. Вы только знаете, что создатель другого объекта подумал, что было бы разумно, если бы вы позвонили Dispose, как только вам больше не понадобится этот объект.

Очень хорошее объяснение Одноразовая узор был дан здесь, на StackOverflow, под редакцией сообщества вики:

Proper use of the IDisposable interface

Довольно часто, а также в упомянутой ссылке, которую я прочитал:

«Вы не знаете порядок, в котором уничтожены два объекта. Вполне возможно, что в коде Dispose() управляемый объект, из которого вы пытаетесь избавиться, больше не существует».

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

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

Если это так, то почему это может быть так, что если я сохраняю ссылку на мой объект до моего завершения, объект, на который я ссылаюсь, уже завершен?

+1

Вот как я это понимаю: если 'Y' содержит единственную ссылку на' X', но 'Y' становится сиротой, то и' Y', и 'X' будут уничтожены. Поскольку заказ не гарантируется, 'X' может быть уничтожен до' Y'. –

+1

Как часто, это сложнее, чем вы думаете. Я ссылаюсь на статью о блоге Эрика Липперта «Где все, что вы знаете, неправильно». http://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/ – Jodrell

ответ

5

Правда находится где-то между этими двумя:

  • Объект не может быть мусора, поэтому возможность объекта больше не «быть там» не так
  • Объект может, если ссылки на него больше отсутствуют из других объектов, не подлежащих окончательной доработке.

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

Если ваше предположение было правильным, тогда вы могли бы создать два объекта, которые ссылаются друг на друга (и имеют финализаторы), и они могут быть собраны в мусор, потому что они никогда не могут быть завершены.

+0

Ваш последний комментарий объяснил бы. По-видимому, даже в начале моей финализации я не уверен, что объекты, которые я создал, все еще там. Это означает, что я не могу много сделать в моей финализации, только убедитесь, что мои неуправляемые объекты удалены. По-видимому, в моем Dispose (false) я не должен использовать какие-либо из моих объектов, даже если бы я сам их создал и не передавал их кому-либо еще. –

+0

@HaraldDutch, если вы пишете код финализатора, который не нужен, если у вас нет неуправляемых ресурсов, вы должны принять «не принимайте ничего». – Jodrell

+0

@HaraldDutch: Это зависит от того, что вы подразумеваете под «использованием». Вы можете предположить, что они существуют, вы просто не можете предположить, что они еще не были доработаны. –

0

Если все сделано правильно, вам не нужно беспокоиться об удалении объектов, которые уже расположены. Каждая реализация Dispose должна просто ничего не делать, если раньше она была утилизирована.

Итак, вы не можете знать, были ли какие-либо дочерние объекты уже установлены или завершены (поскольку порядок завершения является случайным, см. Другое сообщение), но вы можете безопасно называть их методом Dispose в любом случае.

+0

Вы писали: «Каждая реализация Dispose должна просто ничего не делать, если раньше она была утилизирована». Проблема заключается не в том, что объекты, которые я создал, будут ли я должен утилизировать его снова: конечно нет. Проблема в том, что мой объект создал объект, никто об этом не знает. мой объект еще не распорядился, потому что никто не назвал его распоряжаться, если мой объект вызовет удаление объектов, которые он создал, когда он завершен. На моем подоконнике объекта есть ссылка на него, так что другой еще не завершен, правда? –

+0

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

1

Цитирование Eric Lippert's, When everything you know is wrong, part two

Миф: Сохранение ссылки на объект в переменной предотвращает финализатор от запуска, пока переменная жива; локальная переменная всегда жива, по крайней мере, до тех пор, пока элемент управления не покинет блок, в котором было объявлено локальное имя .

{ 
    Foo foo = new Foo(); 
    Blah(foo); // Last read of foo 
    Bar(); 
    // We require that foo not be finalized before Bar(); 
    // Since foo is in scope until the end of the block, 
    // it will not be finalized until this point, right? 
} 

C# спецификация заявляет, что во время выполнения допускаются широкие полномочия для обнаружить при хранении, содержащие ссылки никогда не будут доступно снова, и прекратить лечение, что хранение в качестве корня сборщика мусора. Например, предположим, что у нас есть локальная переменная foo, и в ней написано в верхней части блока. Если джиттер знает, что конкретное чтение является последним считанным этой переменной, переменная может быть юридически удалена из набора корней GC сразу же; ему не нужно ждать, пока элемент управления не покинет область действия переменной . Если эта переменная содержала последнюю ссылку, то GC может обнаружить, что объект недоступен, и сразу же помещает его в очередь финализатора . Используйте GC.KeepAlive, чтобы этого избежать.

Почему этот джиттер имеет эту широту? Предположим, что локальная переменная составляет , зарегистрированную в регистре, необходимую для передачи значения до Blah(). Если foo находится в реестре, что Bar() нужно использовать, нет никакого смысла в сохранении ценности из никогда, чтобы быть читаемым-снова foo в стеке перед тем Bar() будет называется. (Если фактические детали кода, сгенерированного джиттера имеет интерес к вам, see Raymond Chen’s deeper analysis of this issue.)

Дополнительный бонус весело: среда выполнения использует менее агрессивную генерацию кода и менее агрессивный сбор мусора при запуске программы в отладчик, потому что это плохой опыт отладки, когда объекты неожиданно исчезают, даже если переменная , ссылающаяся на объект, находится в области видимости. Это означает, что если у вас есть ошибка , когда объект завершается слишком рано, вы, вероятно, не можете воспроизвести эту ошибку в отладчике!

См. Последний пункт этой статьи для еще более ужасной версии этой проблемы.

+0

Все больше и больше я пришел к выводу, что если мой класс владеет объектами, которые реализуют System.IDisposable, тогда я должен также реализовать System.IDisposable и убедиться, что их Dispose вызывается во время моего Dispose, а не во время моего Finalize. С самого начала моего финализатора я не могу быть уверен, что управляемые объекты, которые у меня есть, еще не завершены, поэтому при завершении я могу получить доступ только к неуправляемым объектам, желательно с помощью вызова Dispose (false) –

+0

@HaraldDutch, который является правильным. Если у вас нет ресурсов, доступных вам, вы должны избегать финализаторов. Если у вас есть переменные-члены типов 'IDisposable', то ваш класс должен реализовать' IDisposable'. – Jodrell

0

После ответов на все вопросы, я создал небольшую программу, которая показывает, что написал Jodrell (спасибо Jodrell!)

  • Объект может быть мусора, как только он не будет использоваться, даже если У меня есть ссылка на это
  • Это будет сделано, если не будет отладки.

Я написал простой класс, который выделяет неуправляемую память и MemoryStream. Последний реализует System.IDisposable.

По мнению каждого из сотрудников StackOverflow, я должен реализовать System.IDisposable и освободить неуправляемую память, а также Dispose управляемый memoryStream, если мой Dispose вызывается, но если мой финализатор вызывается, я должен освобождать неуправляемую память.

Я пишу некоторые диагностические сообщения консоли

class ClassA : System.IDisposable 
{ 
    IntPtr memPtr = Marshal.AllocHGlobal(1024); 
    Stream memStream = new MemoryStream(1024); 

    public ClassA() 
    { 
     Console.WriteLine("Construct Class A"); 
    } 

    ~ClassA() 
    { 
     Console.WriteLine("Finalize Class A"); 
     this.Dispose(false); 
    } 

    public void Dispose() 
    { 
     Console.WriteLine("Dispose()"); 
     this.Dispose(true); 
     GC.SuppressFinalize(this); 
    } 

    public void Dispose(bool disposing) 
    { 
     Console.WriteLine("Dispose({0})", disposing.ToString()); 
     if (!this.IsDisposed) 
     { 
      if (disposing) 
      { 
       Console.WriteLine("Dispose managed objects"); 
       memStream.Dispose(); 
      } 

      Console.WriteLine("Dispose unmanaged objects"); 
      Marshal.FreeHGlobal(memPtr);     
     } 
    } 

    public bool IsDisposed { get { return this.memPtr == null; } } 
} 

Эта программа следит за Dispose шаблон, как описано много раз, а.о. здесь, в StackOverflow в Proper use of the IDisposable interface

, кстати: для простоты я опустил обработку

Простая консольная программа создает объект, не использует это исключение, но сохраняет ссылку на него и заставляет мусор сборщик для сбора:

private static void TestFinalize() 
{ 
    ClassA a = new ClassA() { X = 4 }; 

    Console.WriteLine("Start Garbage Collector"); 
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
    Console.WriteLine("Done"); 
} 

Обратите внимание, что переменная a удерживает ссылку на объект до конца процедуры. Я забыл Утилизацию, поэтому мой финализатор должен позаботиться об утилизации

Назовите этот метод из вашего основного. Запустите сборку (отпустите) из отладчика и запустите ее с помощью командной строки.

  • Если запустить из отладчика, то объект остается в живых до конца процедуры, поэтому, пока сборщик мусора не закончит сбор
  • Если запустить из командной строки, то объект будет завершен до процедуры заканчивается, хотя у меня все еще есть ссылка на объект.

Так Jodrell прав:

  • неуправляемого кода необходимо Dispose() и Finalize, использовать Dispose (Ьоо)

  • Управляемые одноразовые объекты должны Dispose(), предпочтительно через Dispose (BOOL). В Dispose (BOOL) вызывать только Dispose() управляемых объектов при утилизации

  • не доверяют отладчик: он делает, что объекты будут завершены в разные моменты времени, чем без отладчика

0

В большинстве случаи, когда Finalize вызывается на объект, который содержит ссылки на один или несколько IDisposable объектов, один или несколько из следующих действий будут применяться:

  1. другой объект уже был очищен , и в этом случае вызов Dispose в лучшем случае бесполезен.
  2. Другой объект должен быть завершен как можно скорее, но еще нет, и в этом случае вызов Dispose, вероятно, не нужен.
  3. Код очистки другого объекта нельзя безопасно использовать в контексте потоковой обработки финализатора, и в этом случае вызов Dispose может быть катастрофическим.
  4. Другой объект по-прежнему используется кодом в другом месте, и в этом случае звонок Dispose может быть катастрофическим.

Есть несколько ситуаций, когда код знает достаточно о IDisposable объектов, которые он имеет дело, чтобы знать, либо, что ни один из указанных выше не применяются, или что он должен вызвать очистку, несмотря на выше; тем не менее, эти ситуации могут быть лучше удовлетворены тем, что другие объекты поставляют способ , отличный от Dispose, который может завершить вызов объекта.

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