2010-11-23 16 views
5

Una herramienta de análisis estático me sigue diciendo que mi código C# tiene pérdidas de recursos en él.¿Qué objetos no limpia el recolector de basura?

He aquí un ejemplo:

StringReader reader = new StringReader(...); 

// do something with reader 

... 

} // static analysis tool cries that I've leaked **reader** 

es mi herramienta correcta? Si es así, ¿por qué?

Editar (respondiendo al comentario) - Mi herramienta de análisis estático dice que tengo un montón de pérdidas de recursos. Sé por este forum que ciertos objetos Java AWT deben liberarse explícitamente, de lo contrario se produce una fuga. ¿Hay objetos C# que deban liberarse explícitamente?

+0

¿Qué es la herramienta de análisis estático? puede ser más específico – AlwaysAProgrammer

Respuesta

13

Sí, su código está goteando mal. Debería ser así:

using (StringReader reader = new StringReader(...)) 
{ 

} 

Cada clase que implementa IDisposable necesita ser envuelto en una using block para asegurar que el método Dispose siempre se llama.


UPDATE:

Elaboración: en .NET hay la interfaz IDisposable que define el método Dispose. Las clases que implementan esta interfaz (como secuencias de archivos, conexiones de bases de datos, lectores, ...) pueden contener punteros a recursos no administrados y la única manera de garantizar que esos recursos/identificadores no administrados se liberen es llamar al método Dispose. Así que en .NET para garantizar que algún código se llama incluso si se produce una excepción es utilizar un try/finally:

var myRes = new MyResource(); // where MyResource implements IDisposable 
try 
{ 
    myRes.DoSomething(); // this might throw an exception 
} 
finally 
{ 
    if (myRes != null) 
    { 
     ((IDisposable)myRes).Dispose(); 
    } 
} 

Las personas que escriben el código de C# se han dado cuenta rápidamente de que escribir esto cada vez que estaba tratando con una el recurso desechable era un PITA. Así que han introducido el using declaración:

using (var myRes = new MyResource()) 
{ 
    myRes.DoSomething(); // this might throw an exception 
} 

que es un poco más corto.

+0

Darin, ¿podría explicar amablemente su código? Vengo de un fondo de C++. Gracias. –

+0

Tiene una fuga, pero el recolector de basura se ocupará de ello mucho antes de que se convierta en un problema. Sin embargo, el consejo sigue siendo bueno. –

+2

Dices que está goteando "mal", pero en el caso de 'StringReader', no se filtrará nada, es como' MemoryStream' al respecto. Lo mejor es deshacerse de él de todos modos, solo por el bien de las mejores prácticas y en caso de que alguna vez cambie el código para usar algo similar que * necesita * limpiar ... pero en realidad no tiene fugas. –

0

se ha filtrado, pero 'eventualmente' el GC lo limpiará para usted.

+0

Podría decir esto sobre cualquier objeto no explícitamente eliminado, aunque ... – Cameron

1

yo creo que es porque no se ha llamado al método Close después de que haya terminado con él, aunque de acuerdo con MSDN:

Esta implementación de Cerca llama al método Dispose pasando un valor verdadero.

Así que esperaría que cuando el recolector de basura se acerca al lector, será Disposed con el mismo resultado final (y sin pérdida de memoria).

ACTUALIZACIÓN: estoy equivocado, el GC no Disponer automáticamente los objetos IDisposable. Debe llamar Cerrar (o Eliminar) explícitamente.

+0

Si el patrón 'IDisposable' se implementa correctamente, entonces el finalizador de clase (' ~ Class') _will_ se ejecutará, debe tener el mismo efecto que llamar a 'Dispose'. Por supuesto, hay más que eso, que no tengo espacio suficiente para explicar aquí. –

0

Un lector de cadenas podría acceder a una secuencia. Al no deshacerse del lector de cadenas, podría dejar esa secuencia abierta. La secuencia se puede adjuntar a un archivo en el sistema y, a su vez, puede haberlo bloqueado.

intente buscar en la instrucción de uso y que invocará a disponer de usted automáticamente.

using (sr) { 
// your code 
} 
+0

Un lector de cadenas no accederá a una transmisión. –

2

Parece que no está eliminando su StringReader. Debe llamar al .Dispose() para limpiar los recursos no administrados. O mejor aún, utilizar esto en un bloque using como tal:

using (StringReader reader = new StringReader(...)) 
{ 
    // your code 
} 

Esto hará que Dispose() automáticamente al final del bloque, incluso si su código produce una excepción (idéntico al uso de un bloque finally).

0

El recolector de basura recogerá todo lo que ya no tenga referencia. En su muestra, reader se recogerá con el tiempo (aunque, nadie puede decir cuándo).

Sin embargo, la "herramienta de análisis estático" se queja de que no está llamando manualmente al Dispose().

StringReader reader = ... 
... 
reader.Dispose(); 

En este caso específico, probablemente no sea un gran problema. Sin embargo, cuando se trata de muchas clases IO (* Stream, * Reader, etc.), es bueno deshacerse de ellas cuando haya terminado. Se puede utilizar para ayudar a using:

using(StringReader reader = ...) { 
    ... 
} //reader is automatically disposed here 
6

El código no es realmente fugas de cualquier cosa en este caso específico, porque StringReader en realidad no tienen ningún recurso para limpiar, al igual que MemoryStream no. (Con MemoryStream que puede aún así terminar necesidad de disponer de él si lo está utilizando de forma asíncrona o de interacción remota ... pero en los casos sencillos, no importa.)

Sin embargo, es una buena idea deseche cualquier cosa que implemente IDisposable solo por principio. Esto evitará que tenga fugas (quizás temporalmente) recursos no administrados, como manejadores de archivos.

Por ejemplo, supongamos que ha cambiado el código a:

StreamReader reader = new StreamReader("file.txt"); 
... 

Si no cierra o disponer reader aquí (en un bloque finally oa través de un using statement) que llevará a cabo el archivo abierto hasta cualquier tipo está sosteniendo directamente el identificador de archivo OS está finalizado. La eliminación de cosas de manera explícita no solo libera recursos no administrados antes, sino que también quita los objetos finalizables de la cola de finalización, lo que significa que pueden ser recolectados anteriormente.

2

Como han dicho otros, todo se reduce a que el StringReader no se elimina, así que no voy a dar con eso.

Lo que está pasando es que la herramienta de análisis estático es esencialmente una herramienta tonta. Y no me refiero a tonto como si no lo usara, me refiero a tonto ya que está viendo un criterio muy limitado.

En este caso, ve un objeto siendo instanciado cuya clase implementa IDisposable. La herramienta simplemente está buscando si realiza una llamada de eliminación correspondiente antes de que el objeto salga del alcance. Esto se puede hacer diciendo explícitamente object.Dispose(); o mediante la cláusula using (var x = ...) {}.

De acuerdo con MS specs, las clases deben implementar IDisposable en el caso de que manejen recursos no administrados (como identificadores de archivos). Ahora, es posible que desee revisar este MSDN post que habla sobre las clases que implementan IDisposable que no necesariamente tiene tiene para llamar a disponer() en.

Lo que nos deja con dos soluciones viables. El primero (y uno que yo y Darin recomiendo) es envolver siempre un objeto que implementa IDisposable en una cláusula de uso. Es solo una buena práctica. Después de todo, causa cero daños en su lugar, mientras que no tenerlo podría causar muchas pérdidas de memoria (dependiendo de la clase) y estoy not smart enough para recordar cuál es cuál.

El otro sería configurar su herramienta de análisis estático (si es posible) para ignorar tales advertencias. Realmente creo que esto sería una mala idea (tm)

2

Hay muchas clases de transmisiones, con tipos asociados de objetos lectores. Algunas de esas clases manipulan objetos externos de una manera que deben deshacerse antes de que se abandonen por completo, pero no se pueden deshacer cuando todavía se necesitan (por ejemplo, un lector de archivos abrirá un archivo; el archivo se debe cerrar antes de manejar el archivo) se olvida, pero no se puede cerrar hasta que el lector haya terminado con esto). El método "Eliminar" se encargará de todo lo que el lector "vuelva a poner" antes de que se abandone por completo. Debido a que es común que una parte del código cree un lector de flujo y lo transfiera a otra parte del código, y debido a que la primera parte del código puede no tener forma de saber cuándo el código que utiliza el lector termina, el código el uso del lector tiene la responsabilidad de llamar a Dispose en el lector cuando haya terminado.

Tenga en cuenta que algunos tipos de lectores en realidad no hacen nada en su método Dispose, pero cualquier código que acepte tipos arbitrarios de lectores de flujo debería llamarlo. Es posible que mientras su código esperaba un tipo de lector de flujo que no requiera desechar, se le puede otorgar una clase derivada que lo requiera. Llamar a Dispose aun cuando no sea estrictamente necesario protegerá contra tales escenarios.

Cuestiones relacionadas