Подтвердить что ты не робот

Какие объекты я могу использовать в методе финализатора?

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

Я пропустил какой-то вопрос относительно финализаторов и строк?

UPD: Что-то вроде этого:

public class TempFileStream : FileStream
{
    private string _filename;

    public TempFileStream(string filename)
        :base(filename, FileMode.Open, FileAccess.Read, FileShare.Read)
    {
        _filename = filename;
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        if (_filename == null) return;

        try
        {
            File.Delete(_filename); // <-- oops! _filename could be gc-ed already
            _filename = null;
        }
        catch (Exception e)
        {
            ...
        }
    }
}
4b9b3361

Ответ 1

Да, вы можете, безусловно, использовать строки из финализатора и многие другие типы объектов.

Для окончательного источника всего этого я бы взял книгу CLR через С#, 3-е издание, написанную Джеффри Рихтером. В главе 21 это подробно описано.

В любом случае, вот что происходит на самом деле...

Во время сбора мусора любые объекты, которые имеют финализатор, который все еще хочет быть вызванным, помещаются в специальный список, называемый freachable list.

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

Обратите внимание, что это включает в себя строки, которые были вашим вопросом, но также включает в себя все другие типы объектов

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

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

Позвольте мне проиллюстрировать пример, прежде чем я расскажу вам, что не работает.

Скажем, у вас есть объекты от A до Z, и каждый объект ссылается на следующий, так что у вас есть объект A, ссылающийся на объект B, B ссылки C, C ссылки D и т.д. до Z.

Некоторые из этих объектов реализуют финализаторы, и все они реализуют IDisposable. Предположим, что A не реализует финализатор, но B делает, а затем некоторые из остальных делают также, это не важно для этого примера, который выходит за пределы A и B.

Ваша программа держится на ссылке на A и только A.

В обычном и правильном шаблоне использования вы должны распорядиться A, который будет распоряжаться B, который будет распоряжаться C и т.д., но у вас есть ошибка, так что этого не происходит. В какой-то момент все эти объекты имеют право на сбор.

В этот момент GC найдет все эти объекты, но затем заметьте, что B имеет финализатор, и он еще не запущен. Таким образом, GC помещает B в свободный список и рекурсивно возьмет C, D, E и т.д. До Z, вне списка GC, потому что, поскольку B внезапно становится доступным для сбора, так же как и остальные. Обратите внимание, что некоторые из этих объектов также помещаются в сам freachable список, потому что у них есть финализаторы самостоятельно, но все объекты, на которые они ссылаются, выживут GC.

A, однако, собирается.

Позвольте мне сделать вышеприведенный параграф понятным. На этом этапе A было собрано, но B, C, D и т.д. До Z все еще живы, как будто ничего не произошло. Хотя ваш код больше не ссылается ни на один из них, в свободном списке есть.

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

В следующий раз, когда GC запущен, эти объекты теперь собираются.

Так что, конечно, работает, так что же такое большой бруаха?

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

Как я уже говорил выше, в обычном мире вы бы назвали dispose на A, который располагает B, который располагает C и т.д. Если один из этих объектов является потоком, объект, ссылающийся на поток, может в своем вызове Dispose, скажите: "Я просто пойду и сброшу свои буферы перед удалением потока". Это совершенно законно, и многие существующие коды делают это.

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

Другими словами, то, что вы не можете сделать, суммируется следующим образом:

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

Итак, вернемся к вашему вопросу:

Q. Могу ли я использовать строки в методе финализатора?
О. Да, потому что строки не реализуют финализатор и не полагаются на другие объекты, которые имеют финализатор, и, таким образом, будут жить и пинать во время выполнения вашего финализатора.

Предположение о том, что вы сделали неправильный путь, - это второе предложение qustion:

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

Правильное предложение:

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


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


Итак, есть ли когда-нибудь безопасный доступ к объектам, которые также имеют финализатор, из моего собственного финализатора?

Единственный гарантированный безопасный сценарий - это когда ваша библиотека/исходный код программы/класса владеет обоими объектами, чтобы вы знали, что это так.

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

Пример:

У вас есть объект Cache, который записывает данные в файл, этот файл никогда не остается открытым и, таким образом, открыт только тогда, когда объект должен записывать данные на него.

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

CacheManager имеет финализатор. Семантика здесь заключается в том, что если класс менеджера собран, но не удален, он должен удалять кеши, поскольку он не может гарантировать их состояние.

Однако имя файла кэша извлекается из свойства объекта кэша.

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

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

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

Однако, поскольку теперь у вас довольно странная зависимость, я бы, конечно, советовал ей.

Ответ 2

Еще один момент, о котором еще не упоминалось, заключается в том, что, хотя нельзя ожидать, что финализатор объектов будет когда-либо выполняться во время использования объекта, механизм финализации этого не гарантирует. Финализаторы могут выполняться в произвольном неизвестном контексте потоков; как следствие, они должны либо избегать использования каких-либо типов, которые не являются потокобезопасными, либо должны использовать блокировку или другие средства, чтобы гарантировать, что они будут использовать вещи только в потокобезопасном режиме. Примечание. Финализаторы должны использовать Monitor.TryEnter, а не Monitor.Enter, и стремиться действовать как можно более изящно, если блокировка неожиданно удерживается. Обратите внимание: поскольку финализаторы не должны запускаться, пока объект все еще используется, тот факт, что блокировка была неожиданно проведена, часто предполагает, что финализатор запускался раньше. В зависимости от дизайна кода, который использует блокировку, может быть возможно, чтобы финализатор установил флаг и попробовал еще раз, чтобы получить блокировку, и иметь какой-либо другой код, который использует проверку блокировки после выпуска, независимо от того, установлен ли этот флаг и, если да, перерегистрируйте объект для завершения.

Обработка корректной очистки финализации во всех сценариях с потоками сложна. Завершение может показаться сложным, но не существует удобных автоматических механизмов, посредством которых объекты могут гарантировать, что финализаторы не будут работать, пока объекты, о которых идет речь, используются. Следовательно, в финализаторах есть много тонких проблем, связанных с потоком. Код, который игнорирует такие проблемы, будет "обычно" работать, но иногда может оказаться неудачным в трудном для диагностики способом.

Ответ 3

Вы можете вызвать метод dispose внутри вашего финализатора и получить код очистки файла в методе Dispose. Наряду с этим вы также можете передать логическое значение вашему методу dispose, который указывает, что вы вызываете его из финализатора.

Для отличной ссылки на правильное использование Dispose и Fianlizers, прочитайте это Правильное использование интерфейса IDisposable