2008-10-24 24 views
93

tengo el siguiente código:¿Se crea una pérdida de memoria si MemoryStream en .NET no está cerrado?

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

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

¿Hay alguna posibilidad de que el MemoryStream que me ha asignado alguna manera dejar de ser eliminados más tarde?

Tengo una revisión por pares que insiste en que cierre manualmente esto, y no puedo encontrar la información para decir si tiene un punto válido o no.

+35

Pregúntele a su evaluador exactamente * por qué * cree que deberías cerrarlo. Si habla de buenas prácticas generales, probablemente sea inteligente. Si habla de liberar memoria antes, está equivocado. –

Respuesta

48

Si algo es desechable, siempre debe desecharlo. Debería utilizar una instrucción using en su método bar() para asegurarse de que ms2 se disipe.

Eventualmente será limpiado por el recolector de basura, pero siempre es una buena práctica desechar. Si ejecuta FxCop en su código, lo marcará como una advertencia.

+0

Entonces, aunque podría tener un bloque "usar", aún así debería llamar a .Dispose()? –

+13

Las llamadas al bloque de uso se deshacen de usted. – Nick

+0

Tengo que estar en desacuerdo con ese consejo.A menudo, Dispose es solo una operación no operativa y llamarlo solo complica su código. –

5

Todas las transmisiones implementan IDisposable. Envuelva su secuencia de memoria en una declaración de uso y estará bien y elegante. El bloque que usa se asegurará de que tu transmisión se cierre y elimine sin importar nada.

donde sea que llame a Foo puede hacerlo usando (MemoryStream ms = foo()) y creo que todavía debería estar bien.

+1

Un problema que me he encontrado con este hábito es que debes asegurarte de que la transmisión no se esté utilizando en ningún otro lado. Por ejemplo, creé un JpegBitmapDecoder apuntando a un MemoryStream y devolví Marcos [0] (pensando que copiaría los datos en su propia tienda interna) pero encontré que el mapa de bits solo aparecería el 20% del tiempo, resultó que era porque Me estaba deshaciendo de la corriente de memoria. – devios1

+0

Si su flujo de memoria debe persistir (es decir, un bloque de uso no tiene sentido), debe llamar a Dispose e inmediatamente establecer la variable a nulo. Si se deshace de él, ya no está destinado a ser utilizado, por lo que también debe establecerlo como nulo de inmediato. Lo que Chaiguy está describiendo suena como un problema de gestión de recursos, ya que no debe proporcionar una referencia a algo a menos que la cosa que le está entregando asuma la responsabilidad de eliminarlo, y la cosa que distribuye la referencia sabe que ya no es responsable de haciéndolo. – Triynko

1

Si un objeto implementa IDisposable, debe llamar al método .Dispose cuando haya terminado.

En algunos objetos, Eliminar significa lo mismo que Cerrar y viceversa, en ese caso, cualquiera de los dos es bueno.

Ahora, para su pregunta en particular, no, no perderá memoria.

+2

"Must" es una palabra muy fuerte. Siempre que haya reglas, vale la pena conocer las consecuencias de romperlas. Para MemoryStream, hay muy pocas consecuencias. –

-1

No soy un experto en .net, pero tal vez el problema aquí sean los recursos, es decir, el identificador de archivo y no la memoria. Supongo que el recolector de basura eventualmente liberará la secuencia y cerrará el identificador, pero creo que siempre será una buena práctica cerrarla explícitamente, para asegurarse de que elimine el contenido del disco.

+0

Un MemoryStream está todo en la memoria; aquí no hay ningún identificador de archivo. –

3

No es necesario llamar a .Dispose() (o envolver con Using).

La razón por la que llama .Dispose() es a liberar el recurso tan pronto como sea posible.

Piense en términos de, por ejemplo, el servidor Stack Overflow, donde tenemos un conjunto limitado de memoria y miles de solicitudes entrando. No queremos esperar la recolección programada de basura, queremos liberar esa memoria Lo antes posible, está disponible para nuevas solicitudes entrantes.

+21

Llamar a un MemoryStream no va a liberar memoria. De hecho, aún puede obtener los datos en un MemoryStream después de haber llamado a Dispose - pruébelo :) –

+11

-1 Si bien es cierto para un MemoryStream, como consejo general, esto es simplemente incorrecto. Dispose es liberar recursos * no administrados *, como identificadores de archivo o conexiones de bases de datos. La memoria no cae en esa categoría. Casi siempre debe esperar la recolección programada de basura para liberar memoria. – Joe

+0

¿Cuál es la ventaja de adoptar un estilo de codificación para asignar y eliminar objetos 'FileStream' y uno diferente para objetos' MemoryStream'? –

2

No perderá memoria, pero su revisor de código es correcto para indicar que debe cerrar la transmisión. Es educado hacerlo.

La única situación en la que puede perder memoria es cuando accidentalmente deja una referencia a la transmisión y nunca la cierra. Todavía no está realmente perdiendo memoria, pero es y extiende innecesariamente la cantidad de tiempo que dice que lo está usando.

+1

> Todavía no está realmente perdiendo memoria, pero está extendiendo innecesariamente la cantidad de tiempo que dice que lo está usando. ¿Estás seguro? Dispose no libera memoria y llamarlo tarde en la función puede extender el tiempo en que no se puede recopilar. –

+2

Sí, Jonathan tiene un punto. Llamar a Descartar tarde en la función podría hacer que el compilador crea que necesita acceder a la instancia de la transmisión (para cerrarla) muy tarde en la función. Esto podría ser peor que no invocar dispose en absoluto (evitando así una referencia de función tardía a la variable de secuencia), ya que un compilador podría calcular un punto de liberación óptimo (también conocido como "punto de último uso posible") anteriormente en la función . – Triynko

139

No perderá nada, al menos en la implementación actual.

Calling Dispose no limpiará la memoria utilizada por MemoryStream más rápido. Es para evitar que su transmisión sea viable para las llamadas de Lectura/Escritura después de la llamada, lo que puede o no ser útil para usted.

Si está absolutamente seguro de que nunca quiere pasar de un MemoryStream a otro tipo de transmisión, no le hará ningún daño no llamar a Dispose. Sin embargo, en general es una buena práctica, en parte porque si alguna vez cambia para usar un Stream diferente, no querrá ser mordido por un error difícil de encontrar porque eligió la salida fácil desde el principio. (Por otro lado, está el argumento YAGNI ...)

La otra razón para hacerlo es que una nueva implementación puede introducir recursos que se liberarían en Dispose.

+0

En este caso, la función está devolviendo un MemoryStream porque proporciona "datos que se pueden interpretar de manera diferente dependiendo de los parámetros de llamada", por lo que podría haber sido un conjunto de bytes, pero era más fácil por otras razones que hacer como un MemoryStream. Entonces definitivamente no será otra clase de Stream. – Coderer

+0

En ese caso, aún * intentaré * deshacerme de él solo por principio general, crear buenos hábitos, etc., pero no me preocuparía demasiado si se volviera complicado. –

+0

Si uno está realmente preocupado por liberar recursos lo antes posible, anule la referencia inmediatamente después de su bloque "usar", para que los recursos no administrados (si los hay) se limpien y el objeto sea elegible para la recolección de basura. Si el método está regresando de inmediato, probablemente no hará mucha diferencia, pero si continúa haciendo otras cosas en el método como solicitar más memoria, entonces ciertamente puede hacer la diferencia. – Triynko

2

, recomendaría envolviendo el MemoryStream en bar() en un comunicado using principalmente para mantener la coherencia:

  • En este momento MemoryStream no lo hace de memoria libre en .Dispose(), pero es posible que en algún momento en el futuro podría, o usted (u otra persona en su empresa) podría reemplazarlo con su propio MemoryStream personalizado, etc.
  • Ayuda a establecer un patrón en su proyecto para asegurar todos Se eliminan las corrientes - la línea es más firme dibujado diciendo "todas las corrientes deben ser eliminadas" "en lugar de" algunas secuencias deben eliminarse, pero algunas no tienen que "...
  • Si alguna vez cambia el código para permitir la devolución de otros tipos de flujos, tendrá que cambiarlo para eliminarlo de todos modos .

Otra cosa que suele hacer en casos como foo() la hora de crear y devolver un IDisposable es asegurar que cualquier fallo entre la construcción del objeto y de la return es capturado por una excepción, dispone el objeto, y relanzamientos la excepción:

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

La eliminación de recursos no administrados no es determinista en los lenguajes recolectados. Incluso si llama a Dispose explícitamente, no tiene absolutamente ningún control sobre cuándo realmente se libera la memoria de respaldo. Dispose se llama implícitamente cuando un objeto sale del ámbito, ya sea saliendo de una instrucción using o apareciendo la pila de llamadas desde un método subordinado. Dicho todo esto, a veces el objeto puede ser un contenedor para un recurso administrado (por ejemplo, un archivo). Esta es la razón por la cual es una buena práctica cerrar de manera explícita en las sentencias finally o usar la instrucción using. Saludos

+1

No es exactamente cierto. Dispose se llama al salir de una instrucción using. Eliminar no se llama cuando un objeto simplemente sale del alcance. –

8

Esto ya está contestada, pero voy a añadir que el principio de buena pasada de moda de ocultación de información significa que es posible que en algún momento futuro querer refactorizar:

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

a:

Stream foo() 
{  
    ... 
} 

Esto enfatiza que a las personas que llaman no les debería importar qué tipo de flujo se está devolviendo, y hace posible cambiar la implementación interna (por ejemplo, cuando se burla para probar la unidad).

A continuación, tendrá que estar en problemas si no se ha utilizado Desechar en su aplicación barra:

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

Sí hay un fugas, dependiendo de cómo se defina la fuga y cuánto tiempo más tarde se significa ...

Si por fuga quiere decir "la memoria permanece asignada, no disponible para su uso, aunque haya terminado de usarla" y con esto quiere decir en cualquier momento después de llamar a disponer, entonces sí puede haber una fuga , aunque no es permanente (es decir, durante la vida de su aplicación tiempo de ejecución de las licaciones).

Para liberar la memoria administrada utilizada por MemoryStream, , necesita desvincularla, anulando su referencia a la misma, por lo que es elegible para la recolección de basura de inmediato. Si no puede hacer esto, entonces crea una fuga temporal desde el momento en que termina de usarla, hasta que su referencia quede fuera del alcance, porque mientras tanto, la memoria no estará disponible para la asignación.

El beneficio de la sentencia using (simplemente llamando a dispose) es que usted puede DECLARAR su referencia en la instrucción using. Cuando la instrucción using finaliza, no solo se llama a disposer, sino que su referencia queda fuera del alcance, anulando efectivamente la referencia y haciendo que su objeto sea elegible para la recolección de basura inmediatamente sin que tenga que recordar escribir el código "reference = null".

Si bien no dejar de hacer referencia a algo de inmediato no es una pérdida de memoria clásica "permanente", definitivamente tiene el mismo efecto. Por ejemplo, si mantiene su referencia al MemoryStream (incluso después de realizar una llamada a dispose), y un poco más abajo en su método, intenta asignar más memoria ... la memoria en uso por su secuencia de memoria aún referenciada no estará disponible para usted hasta que anule la referencia o se sale del alcance, a pesar de que llamó a disponer y ya lo hizo.

+3

Me encanta esta respuesta. A veces las personas olvidan el doble deber de usar: ansiosa recuperación de recursos * y * eliminación de referencias ansiosa. – Kit

+1

De hecho, aunque oí que a diferencia de Java, el compilador C# detecta "último uso posible", por lo que si la variable está destinada a quedar fuera del alcance después de su última referencia, puede ser elegible para recolección de basura inmediatamente después de su último uso. ... antes de que realmente se salga del alcance. Consulte http://stackoverflow.com/questions/680550/explicit-nulling – Triynko

+1

El recolector de elementos no utilizados y el jitter no funcionan de esa manera. El alcance es una construcción del lenguaje y no algo que el tiempo de ejecución obedecerá. De hecho, probablemente esté alargando el tiempo que la referencia está en la memoria, agregando una llamada a .Dispose() cuando el bloque finalice. Consulte http://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/ –

-4

MemorySteram no es más que una matriz de bytes, que es un objeto gestionado. Olvídese de desechar o cerrar esto no tiene ningún efecto secundario que no sea por encima de la finalización.
Simplemente verifique el método de lavado o reflector de MemoryStream en el reflector y quedará claro por qué no tiene que preocuparse por cerrarlo o desecharlo, solo por cuestiones de buenas prácticas.

+2

-1: si va a publicar en una pregunta de más de 4 años con una respuesta aceptada, intente hacer algo útil. –

Cuestiones relacionadas