Шаблон dispose в C #: зачем нам нужно условие if (dispose)?

Итак, реализация шаблона удаления по умолчанию выглядит так:

class SomeClass : IDisposable
{
   // Flag: Has Dispose already been called?
   bool disposed = false;

   // Public implementation of Dispose pattern callable by consumers.
   public void Dispose()
   { 
      Dispose(true);
      GC.SuppressFinalize(this);           
   }

   // Protected implementation of Dispose pattern.
   protected virtual void Dispose(bool disposing)
   {
      if (disposed)
         return; 

      if (disposing) {
         // Free any other managed objects here.
      }

      // Free any unmanaged objects here.
      disposed = true;
   }

   ~SomeClass()
   {
      Dispose(false);
   }
}

Говорят, что:

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

Возникает вопрос: почему предполагается, что объекты, на которые ссылается объект SomeClass, возможно, уже были освобождены, и мы не должны пытаться удалить их, когда метод вызывается из финализатора? Если на эти объекты по-прежнему ссылается наш SomeClass объект, их нельзя освободить, не правда ли? Говорят, что:

Те, у кого есть ожидающие (невыполненные) финализаторы, остаются в живых (на данный момент) и помещаются в специальную очередь. [...] Перед запуском финализатора каждого объекта он еще активен - эта очередь действует как корневой объект.

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


person Sergey    schedule 09.04.2019    source источник
comment
Просто чтобы бросить здесь гаечный ключ, вы в принципе никогда не должны использовать финализаторы, есть хороший шанс, что если вы его используете, вы делаете что-то не так.   -  person TheGeneral    schedule 09.04.2019
comment
На самом деле это сообщение Стивена Клири IDisposable: то, что ваша мать никогда не говорила вам о распределении ресурсов, он отвечает на все, что вы хотите знать, и все остальное, о чем вы не знали, что вам нужно знать (уровень развлечения 100)   -  person TheGeneral    schedule 10.04.2019
comment
не беспокойтесь, если у вас нет неуправляемых ресурсов, поскольку вам не понадобится финализатор   -  person M.kazem Akhgary    schedule 10.04.2019
comment
Я думаю, что более важным моментом и одним из самых важных правил для финализаторов (кроме №1: не делайте этого) является то, что они не должны касаться / обращаться / использовать какие-либо управляемые объекты. Защита предназначена для обеспечения взаимодействия с неуправляемыми объектами только в том случае, если метод был инициирован финализатором.   -  person pinkfloydx33    schedule 10.04.2019


Ответы (2)


У Конрада Кокосы есть впечатляющее объяснение в своей книге Pro .NET Memory Management < / а>. (курсив добавлен)

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

Поток финализации - еще один поток, созданный средой выполнения .NET. Он удаляет объекты из очереди fReachable по одному и вызывает их финализаторы. Это происходит после того, как сборщик мусора возобновляет управляемые потоки, поскольку коду финализатора может потребоваться выделить объекты. Поскольку единственный корень этого объекта удаляется из очереди fReachable, следующий сборщик мусора, осуждающий создание этого объекта, обнаружит, что он недоступен, и восстановит его.

Более того, очередь fReachable обрабатывается как корень, рассматриваемый на этапе отметки, потому что поток финализатора может быть недостаточно быстрым для обработки всех объектов из него между сборщиками мусора. Это подвергает финализируемые объекты большему кризису Mid-life - они могут оставаться в очереди fReachable некоторое время, потребляя поколение 2 только из-за незавершенной финализации.

Я думаю, что главное здесь:

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

person hardkoded    schedule 09.04.2019
comment
Итак, если эта очередь считается похожей на корневую и Поскольку единственный корень этого объекта удаляется из очереди fReachable, следующий сборщик мусора, осуждающий создание этого объекта, обнаружит, что он недоступен, и вернет его. . Говорят, что осуждает следующий сборщик мусора, а не текущий. Разве это не означает, что внутренние объекты не могут быть удалены перед их владельцем, и мы можем безопасно вызывать метод Dispose для каждого из них из их метода владельца в любом сценарии (включая вызов из финализатора)? - person Sergey; 10.04.2019
comment
Да, но у объектов в очереди fReachable есть только один корень (сама очередь). Они обрабатываются один за другим, без попытки обработать их в каком-либо порядке из-за того, что объекты ссылаются друг на друга - это может потребовать обнаружения довольно сложной упорядоченности. А что тогда с круговыми ссылками? - person Konrad Kokosa; 10.04.2019
comment
Что ж, если я правильно понимаю, отношения собственности больше не имеют значения. IOW все отношения разорваны и каждый объект сам по себе в этой очереди. Я не имел в виду круговые ссылки, но в итоге оказывается, что подход ко всем одинаков. - person Sergey; 10.04.2019

Объекты в .NET существуют, пока на них существуют любые ссылки. Они перестают существовать, как только это делает последняя ссылка. Хранилище, используемое объектом, никогда не будет освобождено, пока объект существует, но есть несколько вещей, которые GC делает перед тем, как освободить хранилище:

  1. Существует специальный список, называемый «очередью финализаторов», в котором хранятся ссылки на все объекты, которые зарегистрировали финализаторы. После идентификации каждой другой ссылки, которая существует где-либо в юниверсе, сборщик мусора проверит все объекты в очереди финализатора, чтобы увидеть, были ли найдены какие-либо ссылки на них. Если этот процесс заставляет его найти объект, который не был обнаружен ранее, он копирует ссылку в другой список, называемый «свободная очередь». Каждый раз, когда свободная очередь не пуста и финализатор не запущен, система будет извлекать ссылку из этой очереди и вызывать для нее finalize.

  2. GC также проверит цели всех слабых ссылок и сделает недействительными любую слабую ссылку, цель которой не была идентифицирована какой-либо активной сильной ссылкой.

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

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

person supercat    schedule 09.04.2019
comment
Я не уверен, что могу это хорошо понять. Считаете ли вы, что объекты, на которые ссылается объект, который в настоящее время завершается, могут быть завершены до их родителя, несмотря на ссылки, которые они имеют от него? - person Sergey; 10.04.2019
comment
Я читаю C # 7.0 in a Nutshell и не вижу там никаких объяснений. И я не смог найти по нему никаких документов с объяснением этого аспекта. Не могли бы вы поделиться ссылкой на документ? - person Sergey; 10.04.2019
comment
@Sergey: У меня нет удобной ссылки, но я думаю, что поиск freachable queue должен дать некоторые идеи. Ключевым моментом является то, что финализация запускается сборщиком мусора, но фактические вызовы finalize не являются частью процесса сборщика мусора, и нет ничего особенного в том, когда время finalize выходит за рамки отсутствия гарантий по этому поводу. Вполне возможно, что к тому времени, когда система дойдет до запуска финализатора, финализатор какого-либо другого объекта может сохранить сильную ссылку на первый объект в месте, доступном для обычного кода, который мог бы начать его использовать. - person supercat; 10.04.2019