Создается ли утечка памяти, если MemoryStream в .NET не закрыта? - PullRequest
103 голосов
/ 24 октября 2008

У меня есть следующий код:

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

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

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

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

Ответы [ 12 ]

153 голосов
/ 24 октября 2008

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

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

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

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

57 голосов
/ 24 октября 2008

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

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

24 голосов
/ 15 мая 2010

Да, есть a утечка , в зависимости от того, как вы определяете УТЕЧКУ и сколько ПОЗЖЕ вы имеете в виду ...

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

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

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

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

8 голосов
/ 16 мая 2009

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

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

до:

Stream foo()
{    
   ...
}

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

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

void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}
5 голосов
/ 24 октября 2008

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

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

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

5 голосов
/ 24 октября 2008

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

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

2 голосов
/ 23 ноября 2008

Я бы рекомендовал обернуть 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;
}
2 голосов
/ 24 октября 2008

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

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

1 голос
/ 24 октября 2008

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

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

Теперь, по вашему конкретному вопросу, нет, вы не потеряете память.

0 голосов
/ 08 января 2013

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

...