É criado um vazamento de memória se um MemoryStream no .NET não for fechado?

112

Eu tenho o seguinte código:

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

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

Existe alguma chance de o MemoryStream que aloquei falhar ao ser descartado posteriormente?

Eu tenho uma revisão por pares insistindo que eu feche manualmente, e não consigo encontrar a informação para dizer se ele tem um ponto válido ou não.

Coderer
fonte
41
Pergunte ao seu revisor exatamente por que ele acha que você deve fechá-lo. Se ele fala sobre boas práticas gerais, provavelmente está sendo inteligente. Se ele fala sobre liberar memória antes, ele está errado.
Jon Skeet,

Respostas:

60

Se algo for descartável, você deve sempre descartá-lo. Você deve usar uma usinginstrução em seu bar()método para garantir que ms2seja descartado.

Eventualmente, ele será limpo pelo coletor de lixo, mas é sempre uma boa prática chamar Dispose. Se você executar o FxCop em seu código, ele será sinalizado como um aviso.

Rob Prouse
fonte
16
O uso de chamadas em bloco dispõe para você.
Nick
20
@Grauenwolf: sua afirmação quebra o encapsulamento. Como consumidor, você não deve se preocupar se é autossuficiente: se for IDisposable, é seu trabalho Dispose ().
Marc Gravell
4
Isso não é verdade para a classe StreamWriter: ela descartará o fluxo conectado apenas se você descartar o StreamWriter - ela nunca descartará o fluxo se for coletado o lixo e seu finalizador for chamado - isso é por design.
springy76,
4
Eu sei que essa pergunta era de 2008, mas hoje temos a biblioteca de tarefas .NET 4.0. Dispose () é desnecessário na maioria dos casos ao usar Tarefas. Embora eu concorde que IDisposable deva significar "É melhor você descartar isso quando terminar", na verdade não significa mais isso.
Phil
7
Outro exemplo de que você não deve descartar o objeto IDisposable é HttpClient aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong Apenas outro exemplo de BCL onde há um objeto IDisposable e você não precisa (ou não deve) descartar isto. Isso é apenas para lembrar que normalmente há algumas exceções à regra geral, mesmo em BCL;)
Mariusz Pawelski
166

Você não vazará nada - pelo menos na implementação atual.

Chamar Dispose não limpará a memória usada pelo MemoryStream mais rápido. Ele vai parar seu fluxo de ser viável para chamadas de leitura / gravação após a chamada, o que pode ou não ser útil.

Se você tem certeza absoluta de que nunca deseja mover de um MemoryStream para outro tipo de fluxo, não fará nenhum mal se não chamar Dispose. No entanto, é geralmente uma boa prática em parte porque se você nunca fazer a mudança de usar um fluxo diferente, você não quer ser mordido por um bug difícil de encontrar, porque você escolheu o caminho mais fácil no início. (Por outro lado, há o argumento YAGNI ...)

A outra razão para fazer isso de qualquer maneira é que uma nova implementação pode introduzir recursos que seriam liberados no Dispose.

Jon Skeet
fonte
Nesse caso, a função está retornando um MemoryStream porque fornece "dados que podem ser interpretados de maneira diferente dependendo dos parâmetros de chamada", então poderia ser uma matriz de bytes, mas era mais fácil por outros motivos como um MemoryStream. Portanto, definitivamente não será outra aula do Stream.
Coderer
Nesse caso, eu ainda tentaria descartá-lo por princípio geral - criar bons hábitos, etc. - mas não me preocuparia muito se se tornasse complicado.
Jon Skeet
1
Se alguém estiver realmente preocupado em liberar recursos o mais rápido possível, anule a referência imediatamente após seu bloco "usando", para que os recursos não gerenciados (se houver algum) sejam limpos e o objeto se torne elegível para a coleta de lixo. Se o método estiver retornando imediatamente, provavelmente não fará muita diferença, mas se você continuar a fazer outras coisas no método, como solicitar mais memória, certamente pode fazer a diferença.
Triynko
@Triynko Não é verdade: consulte: stackoverflow.com/questions/574019/… para obter detalhes.
George Stocker
10
O argumento YAGNI pode ser interpretado em ambos os sentidos - uma vez que decidir não descartar algo que implementa IDisposableé um caso especial que vai contra as melhores práticas normais, você pode argumentar que é esse caso que você não deve fazer até que realmente precise, sob o YAGNI princípio.
Jon Hanna de
26

Sim, há um vazamento , dependendo de como você define LEAK e quanto DEPOIS você quer dizer ...

Se por vazamento você quer dizer "a memória permanece alocada, indisponível para uso, mesmo que você termine de usá-la" e por último você quer dizer a qualquer momento após chamar dispose, então sim, pode haver um vazamento, embora não seja permanente (ou seja, para a vida útil do tempo de execução de seus aplicativos).

Para liberar a memória gerenciada usada pelo MemoryStream, você precisa cancelar a referência, anulando sua referência a ela, para que se torne elegível para a coleta de lixo imediatamente. Se você não conseguir fazer isso, criará um vazamento temporário a partir do momento em que terminar de usá-lo, até que sua referência saia do escopo, porque nesse ínterim a memória não estará disponível para alocação.

O benefício da instrução using (em vez de simplesmente chamar dispose) é que você pode DECLARAR sua referência na instrução using. Quando a instrução using termina, não apenas dispose é chamado, mas sua referência sai do escopo, efetivamente anulando a referência e tornando seu objeto elegível para a coleta de lixo imediatamente sem exigir que você se lembre de escrever o código "reference = null".

Embora deixar de cancelar a referência de algo imediatamente não seja um vazamento de memória "permanente" clássico, definitivamente tem o mesmo efeito. Por exemplo, se você mantiver sua referência ao MemoryStream (mesmo após chamar dispose), e um pouco mais abaixo em seu método, você tenta alocar mais memória ... a memória em uso por seu fluxo de memória ainda referenciado não estará disponível para você até que você anule a referência ou saia do escopo, mesmo que você tenha chamado dispose e tenha terminado de usá-la.

Triynko
fonte
6
Eu amo essa resposta. Às vezes, as pessoas esquecem o duplo dever de usar: a rápida recuperação de recursos e a ansiosa desreferenciação.
Kit
1
Na verdade, embora eu tenha ouvido que, ao contrário do Java, o compilador C # detecta o "último uso possível", portanto, se a variável for destinada a sair do escopo após sua última referência, ela pode se tornar elegível para a coleta de lixo logo após seu último uso possível. . antes de realmente sair do escopo. Consulte stackoverflow.com/questions/680550/explicit-nulling
Triynko
2
O coletor de lixo e o jitter não funcionam dessa forma. O escopo é uma construção de linguagem e não algo que o tempo de execução obedecerá. Na verdade, você provavelmente está prolongando o tempo em que a referência fica na memória, adicionando uma chamada a .Dispose () quando o bloco termina. Ver ericlippert.com/2015/05/18/…
Pablo Montilla
8

Não é necessário chamar .Dispose()(ou agrupar com Using).

O motivo de você ligar .Dispose()é para liberar o recurso o mais rápido possível .

Pense em termos de, digamos, o servidor Stack Overflow, onde temos um conjunto limitado de memória e milhares de solicitações chegando. Não queremos esperar pela coleta de lixo programada, queremos liberar essa memória o mais rápido possível para que esteja disponível para novas solicitações de entrada.

Jeff Atwood
fonte
24
No entanto, chamar Dispose em um MemoryStream não vai liberar memória. Na verdade, você ainda pode obter os dados em um MemoryStream depois de chamar Dispose - experimente :)
Jon Skeet
12
-1 Embora seja verdade para um MemoryStream, como um conselho geral, isso é simplesmente errado. Dispose é liberar recursos não gerenciados , como identificadores de arquivo ou conexões de banco de dados. A memória não se enquadra nessa categoria. Quase sempre, você deve aguardar que a coleta de lixo programada libere memória.
Joe
1
Qual é a vantagem de adotar um estilo de codificação para alocar e descartar FileStreamobjetos e outro diferente para MemoryStreamobjetos?
Robert Rossney,
3
Um FileStream envolve recursos não gerenciados que podem ser liberados imediatamente ao chamar Dispose. Um MemoryStream, por outro lado, armazena uma matriz de bytes gerenciada em sua variável _buffer, que não é liberada na hora do descarte. Na verdade, o _buffer nem mesmo é anulado no método Dispose do MemoryStream, que é um VERGONHOSO BUG IMO porque anular a referência pode tornar a memória elegível para GC no momento do descarte. Em vez disso, uma referência MemoryStream remanescente (mas descartada) ainda se mantém na memória. Portanto, depois de descartá-lo, você também deve anulá-lo se ainda estiver no escopo.
Triynko,
@Triynko - "Portanto, depois de descartá-lo, você também deve anulá-lo se ainda estiver no escopo" - eu discordo. Se for usado novamente após a chamada para Dispose, isso causará uma NullReferenceException. Se não for usado novamente após Dispose, não há necessidade de anulá-lo; o GC é inteligente o suficiente.
Joe
8

Isso já foi respondido, mas vou apenas acrescentar que o bom e antigo princípio da ocultação de informações significa que você pode, em algum momento futuro, querer refatorar:

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

para:

Stream foo()
{    
   ...
}

Isso enfatiza que os chamadores não devem se preocupar com o tipo de fluxo que está sendo retornado e torna possível alterar a implementação interna (por exemplo, ao fazer simulações para testes de unidade).

Você terá problemas se não tiver usado Dispose na implementação de sua barra:

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

Todos os streams implementam IDisposable. Envolva seu fluxo de memória em uma declaração de uso e você ficará bem e elegante. O bloco de uso garantirá que seu stream seja fechado e descartado, não importa o quê.

onde quer que você chame Foo, você pode fazer usando (MemoryStream ms = foo ()) e eu acho que você ainda deve estar bem.

usuario
fonte
1
Um problema que encontrei com esse hábito é que você deve ter certeza de que o stream não está sendo usado em nenhum outro lugar. Por exemplo, eu criei um JpegBitmapDecoder apontado para um MemoryStream e retornou Frames [0] (pensando que ele iria copiar os dados em seu próprio armazenamento interno), mas descobri que o bitmap iria aparecer apenas 20% do tempo - acabou que era porque Eu estava descartando o fluxo de memória.
devios1
Se seu fluxo de memória deve persistir (ou seja, um bloco em uso não faz sentido), você deve chamar Dispose e definir imediatamente a variável como null. Se você descartá-lo, ele não deve ser mais usado, portanto, você também deve defini-lo como null imediatamente. O que chaiguy está descrevendo soa como um problema de gerenciamento de recursos, porque você não deve distribuir uma referência para algo a menos que a coisa para a qual você está entregando assuma a responsabilidade de descartá-la, e a coisa que está distribuindo a referência saiba que não é mais responsável por fazendo isso.
Triynko,
2

Você não perderá memória, mas seu revisor de código está correto ao indicar que você deve fechar o fluxo. É educado fazer isso.

A única situação em que você pode perder memória é quando acidentalmente deixa uma referência ao fluxo e nunca o fecha. Você ainda não está realmente perdendo memória, mas está estendendo desnecessariamente a quantidade de tempo que afirma estar usando.

OwenP
fonte
1
> Você ainda não está realmente perdendo memória, mas está estendendo desnecessariamente a quantidade de tempo que afirma estar usando. Você tem certeza? Dispose não libera memória e chamá-lo mais tarde na função pode, na verdade, estender o tempo em que ela não pode ser coletada.
Jonathan Allen,
2
Sim, Jonathan tem razão. Colocar uma chamada para Dispose mais tarde na função pode realmente fazer com que o compilador pense que você precisa acessar a instância do stream (para fechá-la) muito tarde na função. Isso pode ser pior do que não chamar dispose (evitando assim uma referência no final da função para a variável de fluxo), já que um compilador poderia calcular um ponto de liberação ideal (também conhecido como "ponto do último uso possível") mais cedo na função .
Triynko,
2

Eu recomendaria envolver o MemoryStream bar()em uma usinginstrução, principalmente para consistência:

  • No momento, o MemoryStream não libera memória .Dispose(), mas é possível que em algum momento no futuro isso possa acontecer, ou você (ou outra pessoa em sua empresa) pode substituí-lo por seu próprio MemoryStream personalizado que o faz, etc.
  • Isso ajuda a estabelecer um padrão em seu projeto para garantir que todos os Streams sejam descartados - a linha é traçada com mais firmeza dizendo "todos os Streams devem ser descartados" em vez de "alguns Streams devem ser descartados, mas alguns não precisam" ...
  • Se você alterar o código para permitir o retorno de outros tipos de Streams, será necessário alterá-lo para descartar de qualquer maneira.

Outra coisa que geralmente faço em casos como foo()ao criar e retornar um IDisposable é garantir que qualquer falha entre a construção do objeto e o returnseja capturada por uma exceção, descarta o objeto e relança a exceção:

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;
}
Chris R. Donnelly
fonte
1

Se um objeto implementa IDisposable, você deve chamar o método .Dispose quando terminar.

Em alguns objetos, Dispose significa o mesmo que Close e vice-versa, nesse caso, qualquer um é bom.

Agora, para sua pergunta particular, não, você não vai vazar memória.

Lasse V. Karlsen
fonte
3
"Deve" é uma palavra muito forte. Sempre que houver regras, vale a pena conhecer as consequências de quebrá-las. Para MemoryStream, existem muito poucas consequências.
Jon Skeet,
-1

Não sou especialista em .net, mas talvez o problema aqui sejam os recursos, ou seja, o identificador de arquivo, e não a memória. Acho que o coletor de lixo irá eventualmente liberar o fluxo e fechar o identificador, mas acho que sempre seria uma prática recomendada fechá-lo explicitamente, para garantir que o conteúdo seja descarregado no disco.

Steve
fonte
Um MemoryStream está todo na memória - não há nenhum identificador de arquivo aqui.
Jon Skeet
-2

O descarte de recursos não gerenciados é não determinístico em linguagens com coleta de lixo. Mesmo se você chamar Dispose explicitamente, não terá absolutamente nenhum controle sobre quando a memória auxiliar será realmente liberada. Dispose é implicitamente chamado quando um objeto sai do escopo, seja saindo de uma instrução using ou exibindo a pilha de chamadas de um método subordinado. Dito isso, às vezes o objeto pode realmente ser um invólucro para um recurso gerenciado (por exemplo, arquivo). É por isso que é uma boa prática fechar explicitamente as instruções finally ou usar a instrução using. Felicidades


fonte
1
Não é exatamente verdade. Dispose é chamado ao sair de uma instrução using. Dispose não é chamado quando um objeto simplesmente sai do escopo.
Alexander Abramov,
-3

MemorySteram nada mais é que uma matriz de bytes, que é um objeto gerenciado. Esqueça de descartar ou fechar isso não tem nenhum efeito colateral além do custo da finalização.
Basta verificar o constutor ou o método de descarga de MemoryStream no refletor e ficará claro por que você não precisa se preocupar em fechá-lo ou descartá-lo, a não ser apenas para seguir as boas práticas.

user1957438
fonte
6
-1: Se você for postar uma pergunta de 4 anos ou mais com uma resposta aceita, tente torná-la útil.
Tieson T.