Создается ли утечка памяти, если MemoryStream в .NET не закрыт?


114

У меня такой код:

MemoryStream foo(){
    MemoryStream ms = new MemoryStream();
    // write stuff to ms
    return ms;
}

void bar(){
    MemoryStream ms2 = foo();
    // do stuff with ms2
    return;
}

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

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


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

Ответы:


60

Если что-то одноразовое, вы всегда должны утилизировать это. Вы должны использовать usingоператор в своем bar()методе, чтобы убедиться, что он ms2будет удален.

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


16
Вызовы блока using удаляются за вас.
Ник,

20
@Grauenwolf: ваше утверждение нарушает инкапсуляцию. Как потребителю, вам не нужно беспокоиться о том, что он не работает: если он IDisposable, ваша задача - Dispose ().
Марк Гравелл

4
Это неверно для класса StreamWriter: он удалит подключенный поток только в том случае, если вы удалите StreamWriter - он никогда не удалит поток, если он получит сборщик мусора и вызовет его финализатор - это задумано.
springy76 04

4
Я знаю, что этот вопрос был задан в 2008 году, но сегодня у нас есть библиотека задач .NET 4.0. Dispose () не требуется в большинстве случаев при использовании задач. Хотя я согласен, что IDisposable должен означать «Вам лучше избавиться от этого, когда вы закончите», на самом деле это уже не значит.
Фил

7
Еще один пример, в котором не следует удалять объект IDisposable, - это HttpClient aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong Еще один пример из BCL, где есть объект IDisposable, и вам не нужно (или даже не следует) удалять Это. Просто напомню, что обычно бывают исключения из общего правила, даже в BCL;)
Мариуш Павельски

168

Вы ничего не пропустите - по крайней мере, в текущей реализации.

Вызов Dispose не очистит память, используемую MemoryStream, быстрее. Это будет остановить поток от того , жизнеспособной для чтения / записи вызовов после вызова, которые могут или не могут быть полезными для вас.

Если вы абсолютно уверены, что никогда не хотите переходить от MemoryStream к потоку другого типа, не вызовите Dispose. Тем не менее, это вообще хорошая практика отчасти потому , что если вы когда - либо сделать изменения , чтобы использовать другой поток, вы не хотите , чтобы получить укусили ошибкой труднодоступной находки , потому что вы выбрали легкий путь рано. (С другой стороны, есть аргумент ЯГНИ ...)

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


В этом случае функция возвращает MemoryStream, потому что она предоставляет «данные, которые можно интерпретировать по-разному в зависимости от параметров вызова», поэтому это мог быть массив байтов, но по другим причинам было проще сделать это как MemoryStream. Так что это точно не будет еще один класс Stream.
Coderer

В этом случае я бы все равно попытался избавиться от этого только по общему принципу - выработать хорошие привычки и т. Д. - но я бы не стал слишком беспокоиться, если это станет сложным.
Джон Скит,

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

@Triynko Не совсем верно: См: stackoverflow.com/questions/574019/... для деталей.
Джордж Стокер,

10
Аргумент YAGNI можно рассматривать в обоих направлениях - поскольку решение не избавляться от чего-то, что реализует, IDisposableявляется особым случаем, идущим вразрез с обычной передовой практикой, вы можете утверждать, что это тот случай, который вам не следует делать до тех пор, пока вам действительно не понадобится, в соответствии с YAGNI принцип.
Джон Ханна

26

Да , есть утечка , в зависимости от того, как вы определяете ТРАВИТ и сколько СПУСТЯ вы имеете в виду ...

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

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

Преимущество оператора using (по сравнению с простым вызовом dispose) заключается в том, что вы можете ЗАЯВИТЬ свою ссылку в операторе using. Когда оператор using завершается, вызывается не только dispose, но и ваша ссылка выходит за пределы области видимости, фактически обнуляя ссылку и немедленно делая ваш объект подходящим для сборки мусора, не требуя напоминания о написании кода «reference = null».

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


6
Мне нравится этот ответ. Иногда люди забывают двойную обязанность использования: активное использование ресурсов и активное разыменование.
Kit

1
В самом деле, хотя я слышал, что в отличие от Java, компилятор C # определяет «последнее возможное использование», поэтому, если переменная должна выйти за пределы области действия после последней ссылки, она может получить право на сборку мусора сразу после последнего возможного использования. ... до того, как он действительно выйдет за рамки. См stackoverflow.com/questions/680550/explicit-nulling
Triynko

2
Сборщик мусора и джиттер так не работают. Область видимости - это языковая конструкция, а не то, чему будет подчиняться среда выполнения. Фактически, вы, вероятно, увеличиваете время, в течение которого ссылка находится в памяти, добавляя вызов .Dispose () по завершении блока. См. Ericlippert.com/2015/05/18/…
Пабло Монтилья

8

Вызов .Dispose()(или заворачивание Using) не требуется.

Причина, по которой вы звоните, .Dispose()- как можно скорее освободить ресурс .

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


24
Однако вызов Dispose в MemoryStream не освобождает память. Фактически, вы все еще можете получить данные в MemoryStream после вызова Dispose - попробуйте :)
Джон Скит,

12
-1 Хотя это верно для MemoryStream, в качестве общего совета это просто неверно. Dispose предназначен для освобождения неуправляемых ресурсов, таких как дескрипторы файлов или подключения к базе данных. Память не попадает в эту категорию. Вам почти всегда следует дождаться запланированной сборки мусора, чтобы освободить память.
Джо

1
В чем преимущество принятия одного стиля кодирования для выделения и размещения FileStreamобъектов и другого стиля для MemoryStreamобъектов?
Роберт Россни

3
FileStream включает в себя неуправляемые ресурсы, которые фактически могут быть немедленно освобождены после вызова Dispose. MemoryStream, с другой стороны, хранит управляемый массив байтов в своей переменной _buffer, которая не освобождается во время использования. Фактически, _buffer даже не обнуляется в методе Dispose MemoryStream, что является ПОЗОРНОЙ ОШИБКОЙ IMO, потому что обнуление ссылки может сделать память пригодной для GC прямо во время удаления. Вместо этого устаревшая (но удаленная) ссылка на MemoryStream по-прежнему сохраняется в памяти. Следовательно, после его удаления вы также должны обнулить его, если он все еще находится в области действия.
Трийнко

@Triynko - «Следовательно, как только вы избавитесь от него, вы также должны обнулить его, если он все еще находится в сфере действия» - я не согласен. Если он снова используется после вызова Dispose, это вызовет исключение NullReferenceException. Если он больше не используется после Dispose, обнулять его нет необходимости; GC достаточно умен.
Джо

8

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

MemoryStream foo()
{    
    MemoryStream ms = new MemoryStream();    
    // write stuff to ms    
    return ms;
}

кому:

Stream foo()
{    
   ...
}

Это подчеркивает, что вызывающим абонентам не нужно заботиться о том, какой тип Stream возвращается, и дает возможность изменить внутреннюю реализацию (например, при имитации для модульного тестирования).

Тогда у вас могут возникнуть проблемы, если вы не использовали Dispose в своей реализации панели:

void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}

5

Все потоки реализуют IDisposable. Оберните свой поток памяти в оператор using, и все будет в порядке. Блок using гарантирует, что ваш поток будет закрыт и удален, несмотря ни на что.

где бы вы ни вызывали Foo, вы можете использовать (MemoryStream ms = foo ()), и я думаю, что все должно быть в порядке.


1
Одна проблема, с которой я столкнулся с этой привычкой, заключается в том, что вы должны быть уверены, что поток больше нигде не используется. Например, я создал JpegBitmapDecoder, указывающий на MemoryStream, и вернул Frames [0] (полагая, что он скопирует данные в собственное внутреннее хранилище), но обнаружил, что растровое изображение будет отображаться только в 20% случаев - оказалось, что это было потому, что Я избавлялся от потока памяти.
devios1

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

2

У вас не будет утечки памяти, но ваш рецензент кода правильно указал, что вам следует закрыть поток. Это вежливо.

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


1
> У вас по-прежнему не происходит утечки памяти, но вы без нужды увеличиваете время, которое вы утверждаете, что используете ее. Ты уверен? Dispose не освобождает память, и ее вызов в конце функции может фактически продлить время, в течение которого она не может быть собрана.
Джонатан Аллен,

2
Да, Джонатан прав. Размещение вызова Dispose в конце функции может фактически заставить компилятор подумать, что вам нужно получить доступ к экземпляру потока (чтобы закрыть его) очень поздно в функции. Это может быть хуже, чем вообще не вызывать dispose (что позволяет избежать ссылки на переменную потока с задержкой в ​​функции), поскольку в противном случае компилятор мог бы вычислить оптимальную точку выпуска (также известную как «точка последнего возможного использования») ранее в функции. .
Трийнко

2

Я бы рекомендовал обертывание MemoryStream в bar()в usingзаявлении , главным образом , для последовательности:

  • Прямо сейчас MemoryStream не освобождает память .Dispose(), но возможно, что в какой-то момент в будущем это может, или вы (или кто-то другой в вашей компании) можете заменить ее своим собственным MemoryStream, который делает, и т. Д.
  • Это помогает установить шаблон в вашем проекте, чтобы гарантировать, что все потоки будут уничтожены - линия более четко нарисована, говоря «все потоки должны быть удалены» вместо «некоторые потоки должны быть удалены, но некоторые не должны» ...
  • Если вы когда-нибудь измените код, чтобы разрешить возврат других типов потоков, вам все равно придется изменить его, чтобы удалить.

Еще одна вещь, которую я обычно делаю в таких случаях, как foo()создание и возврат IDisposable, - это обеспечение того, чтобы любой сбой между созданием объекта и returnобъектом был перехвачен исключением, удалялся объект и повторно генерировался исключение:

MemoryStream x = new MemoryStream();
try
{
    // ... other code goes here ...
    return x;
}
catch
{
    // "other code" failed, dispose the stream before throwing out the Exception
    x.Dispose();
    throw;
}

1

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

В некоторых объектах Dispose означает то же, что и Close, и наоборот, в этом случае подходит любой вариант.

Теперь, по вашему конкретному вопросу, нет, утечки памяти не будет.


3
«Должен» - очень сильное слово. Когда есть правила, стоит знать о последствиях их нарушения. Для MemoryStream последствий очень мало.
Джон Скит,

-1

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


MemoryStream находится в памяти - здесь нет дескриптора файла.
Джон Скит,

-2

Удаление неуправляемых ресурсов в языках со сборкой мусора не является детерминированным. Даже если вы вызываете Dispose явно, у вас нет абсолютно никакого контроля над фактическим освобождением резервной памяти. Dispose неявно вызывается, когда объект выходит за пределы области видимости, будь то выход из оператора using или всплытие стека вызовов из подчиненного метода. При этом, иногда объект может фактически быть оболочкой для управляемого ресурса (например, файла). Вот почему рекомендуется явно закрывать в операторах finally или использовать оператор using. ура


1
Не совсем так. Dispose вызывается при выходе из оператора using. Dispose не вызывается, когда объект просто выходит за пределы области видимости.
Александр Абрамов

-3

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


6
-1: Если вы собираетесь опубликовать вопрос давности 4+ с принятым ответом, постарайтесь сделать его чем-нибудь полезным.
Tieson T.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.