Сборка мусора с помощью IDisposable

Я разговаривал с человеком об использовании инструкции().

Он сказал, что если мы НЕ будем использовать оператор using() для чего-то вроде StreamWriter, если произойдет какое-либо исключение, ресурс НИКОГДА не будет собран.

Я понимаю, что нужно использовать оператор using(), но я не согласен с тем, что ресурс никогда не будет собран. Я думаю, что оператор using() вызовет метод dispose() в конце, что может значительно ускорить сбор. Однако, даже если мы не используем using(), мы не вызываем dispose(), ресурс все равно может быть собран путем сбора мусора, хотя это может занять гораздо больше времени.

С кем ты согласен?

пс. Я знаю, что вы все говорите. Важно использовать оператор using(). Я просто хочу узнать, точно ли ресурс никогда не будет собран, если мы этого не сделаем?


person Frank    schedule 26.05.2011    source источник
comment
С кем ты согласен? Ни то, ни другое :) Можно создать одноразовый компонент, чтобы метод Dispose() не вызывался из деструктора С#, и неуправляемые ресурсы не освобождались. С другой стороны, вы правы: MS предлагает шаблон, в котором вы всегда вызываете Dispose(bool) из 2 мест: из Dispose() и из деструктора. Предполагая, что StreamWriter следует этому шаблону, сборщик мусора инициирует освобождение неуправляемых ресурсов.   -  person DK.    schedule 27.05.2011
comment
Аналогично: IDisposable, действительно ли это важно   -  person Henk Holterman    schedule 27.05.2011
comment
Короче говоря: вы правы.   -  person Hans Passant    schedule 27.05.2011


Ответы (7)


Давайте проясним это. Предположим, что рассматриваемый ресурс является дескриптором файла. Сборщик мусора ничего не знает ни об дескрипторе файла, ни о том, как его освободить. Дескриптор файла — это просто целое число.

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

Это все ясно? Сборщик мусора не освобождает ресурс; единственный ресурс, о котором знает сборщик мусора, — это объекты в памяти. Прежде чем объект будет освобожден, он финализируется, поэтому сам объект знает, как освободить ресурс.

person Eric Lippert    schedule 26.05.2011

using(x) — это детерминированный шаблон. Это гарантирует, что Dispose() вызывается в реализаторе при прохождении определенной точки в потоке выполнения. Сборщик мусора, с другой стороны, не является детерминированным, потому что вы точно не знаете, когда объект будет фактически удален. using гарантирует, что объект выполнит свой код очистки (что бы это ни было) тогда, когда вы этого ожидаете, а не когда-то в будущем.

person kprobst    schedule 26.05.2011
comment
Точно. И следствием недетерминированного удаления StreamWriter является то, что базовый дескриптор файла остается открытым до тех пор, пока не будет собран мусор. Производит большие ошибки, если у вас есть свободное время. - person ; 27.05.2011

Если больше нет ссылки на этот StreamWriter, он в конечном итоге будет удален сборщиком мусора, но это зависит от сборщика мусора, когда это происходит - это не детерминировано, поэтому вы всегда должны использовать блоки using, когда можете.

person BrokenGlass    schedule 26.05.2011

Вы должны всегда вызывать Dispose(), когда закончите использовать объект IDisposable. Заявление using — отличный инструмент для того, чтобы убедиться, что вы соблюдаете это правило.

Цель IDisposable — разрешить классам удалять выделенные неуправляемые ресурсы, которые не будут автоматически очищаться при сборке мусора.

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

Вот почему существует оператор using; он обеспечивает удобный синтаксис для использования объектов IDisposable и определяет четкую область применения этих объектов.

Также обратите внимание, что сборщик мусора никогда не вызывает сам Dispose(), но также обратите внимание, что рекомендуется следовать Завершить/удалить шаблон, как описано в MSDN. Если объект соответствует шаблону Finalize/Dispose, Dispose() будет вызываться, когда сборщик мусора вызывает финализатор.

person Håvard S    schedule 26.05.2011
comment
Да, но сборщик мусора вызывает Dispose() для объектов, которые он собирает. Таким образом, неуправляемые ресурсы будут в конечном итоге собраны, за исключением нескольких необычных крайних случаев. - person JSBձոգչ; 27.05.2011
comment
Нет, GC не вызывает Dispose(). Разработчикам рекомендуется следовать шаблону Finalize/Dispose, где финализатор вызывает Dispose(false). Однако нет никакой гарантии, что реализация действительно делает это. - person Håvard S; 27.05.2011
comment
Этого не произойдет, если вы не вызовете Dispose в финализаторе. И финализаторы не вызываются надежно при сборе объекта. - person Kevin Hsu; 27.05.2011
comment
@Kevin, финализаторы надежно вызываются при сборе объекта. Какой в ​​них смысл, если они иногда не бегают? При этом вы не знаете, когда это будет вызвано. Возможно, что финализаторы некоторых ваших объектов будут вызываться только при закрытии программы. - person svick; 27.05.2011
comment
@svick, в основном это была моя точка зрения. Когда объект собран, вы не можете рассчитывать на своевременный вызов финализатора. Кроме того, вы не можете полагаться на то, что он когда-либо будет вызван, поскольку процесс может неожиданно завершиться. Если при этом внешние ресурсы остаются заблокированными, вы обречены. - person Kevin Hsu; 27.05.2011
comment
@ Кевин, если процесс неожиданно завершится, ты ни на что не можешь положиться. Итак, да, возможно, финализатор никогда не вызывается, но то же самое относится к Dispose() при правильном использовании или к любому другому способу высвобождения ресурсов. - person svick; 27.05.2011
comment
@svick, для Dispose () это будет менее вероятно. С финализатором время между получением и выпуском может быть неопределенным, в то время как Dispose будет максимально своевременным. - person Kevin Hsu; 27.05.2011

Я разговаривал с человеком об использовании инструкции(). Он сказал, что если мы НЕ будем использовать оператор using() для чего-то вроде StreamWriter, если произойдет какое-либо исключение, ресурс НИКОГДА не будет собран.

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

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

О, тогда ты прав.

Я думаю, что оператор using() вызовет метод dispose() в конце, что может значительно ускорить сбор.

Это может или не может сделать сбор быстрее. Обычно метод dispose вызывает GC.SupressFinalize(object), что означает, что финализатор не будет вызываться при сборке мусора. Вместо этого объект будет просто собран. Так что это может сделать сбор быстрее.

Если вы хотите сказать, что это заставляет объект собираться немедленно, а не позже, то это будет неверно. Подходящие объекты собираются всякий раз, когда к ним обращается сборщик мусора, никогда раньше, и объект становится подходящим, как только у него нет активных ссылок, на что оператор using мало влияет. На самом деле, поскольку блок finally в операторе using содержит живую ссылку, я могу представить сценарии, в которых он мог бы увеличить время жизни объекта, но этот эффект не рассматривается, поскольку управление временем жизни объекта это не точка использования. Детерминированная утилизация неуправляемых ресурсов — это смысл оператора using.

Однако, даже если мы не используем using(), мы не вызываем dispose(), ресурс все равно может быть собран путем сбора мусора, хотя это может занять гораздо больше времени.

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

person Jeffrey L Whitledge    schedule 26.05.2011

использование по существу такое же, как следующее (проверьте IL обоих, если вы сомневаетесь в этом):

 try
 { 
      IDisposable o = new Object();
 }
 finally
 {
      o.Dispose();
 }

Пока рассматриваемый объект реализует IDisposable, будет вызываться метод Dispose(), а ресурс, ожидающий чего-то глупого, закодированного в Dispose(), будет собирать мусор. Когда? Не вопрос, на который я могу ответить.

Возможно ли, что элемент никогда не будет GCed. Ну, никогда — это долго, но теоретически возможно, если выйдет за рамки первого поколения и просто останется там. Проблема? Нет, если память со временем понадобится, она будет очищена. Часто неправильно воспринимается как утечка памяти? Несомненно.

person Gregory A Beamer    schedule 26.05.2011
comment
Если бы это было возможно, я бы дважды проголосовал за этот ответ :-) Отличное объяснение слишком распространенного заблуждения. - person DaveRead; 27.05.2011
comment
Только одна придирчивая деталь: переменная o должна быть объявлена ​​вне блока try, иначе она не будет видна внутри блока finally. - person KeithS; 27.05.2011
comment
Кроме того, сгенерированный код компилятора выполнит нулевую проверку o перед вызовом Dispose. - person vcsjones; 27.05.2011
comment
Этот ответ неверен; GC не вызывает Dispose() вместо вас. - person Håvard S; 27.05.2011
comment
@ Хавард. Это зависит. Если объект имеет неуправляемые ресурсы и шаблон IDisposable реализован правильно, сборщик мусора вызовет для вас косвенно Dispose(). Если неуправляемых ресурсов нет, то вы правы, сборщик мусора не будет вызывать Dispose, но на самом деле это не проблема, поскольку все это будут управляемые ресурсы, которые в конечном итоге будут собраны тем или иным образом, как только не будет действительных ссылок на объект. - person InBetween; 27.05.2011
comment
@Håvard S: Нет, это не так; блок кода using фактически вызовет Dispose() для переменной, объявленной в круглых скобках блока. Это его цель. - person KeithS; 27.05.2011
comment
GC не знает, есть ли какие-либо неуправляемые ресурсы, от которых можно избавиться. Он вызывает финализатор, и если финализатор вызывает Dispose() в соответствии с моим ответом, в конечном итоге все будет в порядке. Но нет никаких гарантий. - person Håvard S; 27.05.2011
comment
@KeithS Я имею в виду не его заявления об утверждении using, которые, очевидно, верны, а скорее последствия ниже этого Dispose(), которые в конечном итоге будут вызваны GC. Это ложное предположение. - person Håvard S; 27.05.2011

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

Оглядываясь назад, StreamWriter, вероятно, должен был иметь реализацию IDisposable, которая ничего не делает, но иметь класс-потомок StreamOwningWriter, реализация которого будет удалять переданный поток. В качестве альтернативы он мог бы иметь параметр конструктора и свойство, чтобы указать, следует ли удалять поток при вызове его собственного метода Dispose. Если бы был выбран любой из этих подходов, правильным поведением всегда был бы вызов Dispose для StreamWriter (и пусть «StreamWriter» (который может быть либо StreamWriter, либо StreamOwningWriter) беспокоится о том, действительно ли Dispose должен что-то делать) .

person supercat    schedule 27.05.2011