2008-12-01 14 views
67

Si entiendo correctamente, el tiempo de ejecución de .NET siempre se limpiará después de mí. Entonces, si creo objetos nuevos y dejo de hacer referencia a ellos en mi código, el tiempo de ejecución limpiará esos objetos y liberará la memoria que ocuparon.Dado que .NET tiene un recolector de basura ¿por qué necesitamos finalizadores/destructores/disposición de patrón?

Dado que este es el caso, ¿por qué entonces algunos objetos necesitan tener un método de destrucción o eliminación? ¿No limpiará el tiempo de ejecución después de ellos cuando ya no se les haga referencia?

Respuesta

93

Los finalizadores son necesarios para garantizar la liberación de recursos escasos de vuelta al sistema, como manejadores de archivos, sockets, kernel, etc. Dado que el finalizador siempre se ejecuta al final del objetos de la vida, es el lugar designado para liberar esos mangos.

El patrón Dispose se utiliza para proporcionar la destrucción determinística de los recursos. Como el recolector de elementos no utilizados en tiempo de ejecución de .net no es determinista (lo que significa que nunca puede estar seguro de cuándo el tiempo de ejecución recogerá los objetos antiguos y llamará a su finalizador), se necesitaba un método para garantizar la liberación determinista de los recursos del sistema. Por lo tanto, cuando implementa correctamente el patrón Dispose, proporciona una liberación determinística de los recursos y, en los casos en que el consumidor es descuidado y no elimina el objeto, el finalizador lo limpiará.

Un simple ejemplo de por qué Dispose se necesita podría ser un método de registro rápido y sucio:

public void Log(string line) 
{ 
    var sw = new StreamWriter(File.Open(
     "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)); 

    sw.WriteLine(line); 

    // Since we don't close the stream the FileStream finalizer will do that for 
    // us but we don't know when that will be and until then the file is locked. 
} 

En el ejemplo anterior, el archivo permanecerá bloqueado hasta que el recolector de basura llama el finalizador en el objeto StreamWriter. Esto presenta un problema ya que, mientras tanto, el método podría llamarse nuevamente para escribir un registro, pero esta vez fallará porque el archivo todavía está bloqueado.

La forma correcta es disponer el objeto cuando ha dejado de utilizarla:

public void Log(string line) 
{ 
    using (var sw = new StreamWriter(File.Open(
     "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))) { 

     sw.WriteLine(line); 
    } 

    // Since we use the using block (which conveniently calls Dispose() for us) 
    // the file well be closed at this point. 
} 

Por cierto, técnicamente finalizadores y destructores significan lo mismo; Prefiero llamar finalizadores de C# 'finalizadores' ya que de lo contrario tienden a confundir a las personas con destructores de C++, que a diferencia de C#, son deterministas.

+4

IMO esta es la mejor respuesta aquí. La parte más importante de esto, y por qué utilizamos la sintaxis desechable, es proporcionar la * liberación determinista * de recursos escasos. Buena publicación. – Rob

+1

Buena respuesta, aunque los finalizadores no se ejecutan automáticamente al final de la vida de los objetos. De lo contrario, no necesitaríamos el patrón desechable. GC los llama cuando determina que necesita ejecutarlos (quién sabe cuándo). –

+1

Solo para el registro. No se garantiza la ejecución de los finalizadores. Se ejecutan secuencialmente por un hilo dedicado, por lo que si un finalizador ingresa en un punto muerto, no se ejecutarán otros finalizadores (y se perderá memoria). Obviamente, el finalizador no debería bloquear, pero solo estoy diciendo que hay advertencias. –

0

Es posible que algunos objetos necesiten limpiar elementos de bajo nivel. Como hardware que debe cerrarse, etc.

1

Los objetos que necesitan descructors y disponen de métodos utilizan recursos no gestionados. Entonces el recolector de basura no puede limpiar esos recursos, y usted tiene que hacer esto por su cuenta.

Mire la documentación de MSDN para IDisposable; http://msdn.microsoft.com/en-us/library/system.idisposable.aspx

El ejemplo utiliza un controlador no administrado - IntPr.

+0

El GC PUEDE limpiar los recursos, simplemente no sabe cuándo. –

+2

El GC CAN * generalmente * limpia los recursos, pero no siempre. Por ejemplo, en la documentación de MSDN para System.DirectoryServices.SearchResultCollection: "Debido a restricciones de implementación, la clase SearchResultCollection no puede liberar todos sus recursos no administrados cuando se recolecta basura" – Joe

0

Principalmente para código no administrado e interacción con código no administrado. El código administrado "puro" nunca debería necesitar un finalizador. Desechable por otro lado es solo un patrón útil para forzar que algo sea liberado cuando hayas terminado con él.

9

El recolector de basura solo se ejecutará si el sistema no está bajo presión de memoria, a menos que realmente necesite liberar algo de memoria. Eso significa que nunca se puede estar seguro de cuándo se ejecutará el GC.

Ahora, imagine que es una conexión de base de datos. Si deja que el GC limpie después de usted, puede estar conectado a la base de datos durante mucho más tiempo de lo necesario, lo que causa una situación de carga extraña.En ese caso, desea implementar IDisposable, para que el usuario pueda llamar a Dispose() o usar using() para asegurarse realmente de que la conexión se cierre lo antes posible sin tener que depender de GC, que puede ejecutarse mucho más tarde.

Generalmente, IDisposable se implementa en cualquier clase que funcione con recursos no administrados.

+1

INCORRECT => "El recolector de basura solo se ejecutará si el sistema no está bajo presión de memoria, a menos que realmente necesite liberar algo de memoria ". En realidad, esta declaración no es verdadera. GC se ejecuta en 3 casos (solo uno es determinista): 1) cuando se solicita la asignación de memoria y se excede el tamaño del segmento actual para esa generación de objetos, 2) el sistema está bajo presión de memoria (OS), 3) AppDomain está siendo descargado –

+1

INCORRECTO => "En general, IDisposable se implementa en cualquier clase que funcione con recursos no administrados." Esta declaración tampoco es verdadera. El patrón IDisposable debe implementarse cada vez que un miembro de la clase implementa IDisposable y SIEMPRE cuando se trata de un recurso no administrado –

0

El recolector de basura .NET sabe cómo manejar los objetos administrados dentro del tiempo de ejecución de .NET. Pero el patrón Dispose (IDisposable) se usa principalmente para objetos no administrados que usa una aplicación.

En otras palabras, el tiempo de ejecución .NET no necesariamente sabe cómo tratar con cada tipo de dispositivo o dispositivo (cierre de conexiones de red, manejadores de archivos, dispositivos gráficos, etc.), por lo que el uso de IDisposable proporciona una forma de diga "permítanme implementar algo de limpieza propia" en un tipo. Al ver esa implementación, el recolector de basura puede llamar a Dispose() y asegurarse de que las cosas que están fuera del montón administrado se limpien.

+0

Gracias ... aclarado al cambiar "fuera de .NET stack/heap" a "managed heap". –

0

Hay algunos (muy pocos) casos en los que puede ser necesario realizar una acción específica cuando ya no se usa un objeto administrado puro, no puedo dar con un ejemplo en la parte superior de mi cabeza, pero he visto un par de usos legítimos a través de los años. Pero la razón principal es limpiar cualquier recurso no administrado que el objeto pueda estar usando.

Por lo tanto, en general, no necesitará usar el patrón Dispose/Finalize a menos que esté utilizando recursos no administrados.

4
  1. hay cosas que el recolector de basura no se puede limpiar después de que
  2. Incluso con cosas que puede limpieza, se lo puede ayudar a limpiar antes
0

Debido a que el colector de la basura no puede recoger lo que el entorno administrado no asignó. Por lo tanto, cualquier llamada a una API no administrada que dé como resultado una asignación de memoria debe recopilarse a la manera antigua.

2

La verdadera razón es porque .net recolección de basura NO está diseñada para recolectar recursos no administrados, por lo tanto, la limpieza de estos recursos sigue en manos del desarrollador. Además, los finalizadores de objetos no se invocan automáticamente cuando el objeto queda fuera del alcance. Son llamados por el GC en algún momento indeterminado. Y cuando se les llama, GC no se ejecuta de inmediato, espera a la siguiente ronda para llamarlo, lo que aumenta el tiempo para limpiar aún más, lo cual no es bueno cuando los objetos tienen pocos recursos no administrados (como archivos). o conexiones de red). Ingrese el patrón desechable, donde el desarrollador puede liberar manualmente recursos escasos en un momento determinado (cuando llama a yourobject.Dispose() o la instrucción using (...)). Tenga en cuenta que debe llamar a GC.SuppressFinalize (this); en su método de desecho para decirle al GC que el objeto se desechó manualmente y no debe finalizarse. Le sugiero que eche un vistazo al libro de Directrices de diseño del marco por K. Cwalina y B. Abrams. Explica el patrón Desechable muy bueno.

¡Buena suerte!

20

Las respuestas anteriores son buenas pero permítanme enfatizar el punto importante aquí una vez más. En particular, usted dijo que

Si entiendo correctamente, el tiempo de ejecución de .NET siempre se limpiará después de mí.

Esto es solo parcialmente correcto. De hecho, .NET solo ofrece administración automática para un recurso en particular: memoria principal. Todos los otros recursos necesitan limpieza manual. 1)

Curiosamente, la memoria principal obtiene un estado especial en casi todas las discusiones sobre los recursos del programa. Por supuesto, hay una buena razón para esto: la memoria principal suele ser el recurso más escaso. Pero vale la pena recordar que también hay otros tipos de recursos que también necesitan administración.


1) El intento de solución habitual es para acoplar el tiempo de vida de otros recursos para el tiempo de vida de posiciones de memoria o identificadores en el código - de ahí la existencia de finalizadores.

+1

¡Podría mejorar esa nota mencionando que es la solución incorrecta! Los productos fungibles y no fungibles deben manejarse de manera diferente. –

+0

Earwicker: estoy de acuerdo contigo. Sin embargo, dado que no conozco ningún lenguaje que implemente una alternativa viable, realmente no sé qué sería mejor. Especialmente dado que cada recurso está ligado a un identificador de todos modos, y ese identificador tiene la misma duración que su memoria. –

+0

La palabra clave que usa C# es una alternativa viable: cuando la ejecución abandona el bloque de código, es hora de liberar el recurso. Esto es preferible para los recursos no fungibles que atar sus vidas a algo fungible como la memoria liberada. –

2

La explicación simplista:

  • Desechar está diseñado para determinista disposición de recursos no-memoria, especialmente recursos escasos. Por ejemplo, un identificador de ventana o una conexión de base de datos.
  • Finalizar está diseñado para no determinista eliminación de recursos que no son de memoria, normalmente como un bloqueo si no se llamó a Dispose.

Algunas pautas para la realización del procedimiento Finalizar:

  • Sólo implementan Finalizar en los objetos que requieren finalización, porque hay un costo de rendimiento asociados con los métodos de finalización.
  • Si necesita un método Finalize, considere la posibilidad de implementar IDisposable para permitir a los usuarios de su tipo evitar el costo de invocación del método Finalize.
  • Sus métodos de finalización deben estar protegidos en lugar de públicos.
  • Su método Finalize debe liberar todos los recursos externos que posea el tipo, pero solo los que posee. No debe hacer referencia a ningún otro recurso.
  • CLR no garantiza el orden en que se invocan los métodos de Finalización. Como señala Daniel en su comentario, esto significa que un método Finalize no debería acceder a ningún tipo de referencia miembro, si es posible, porque estos pueden tener (o pueden tener algún día) sus propios finalizadores.
  • Nunca llame a un método de Finalizar directamente en cualquier tipo que no sea el tipo de base del tipo.
  • Trate de evitar cualquier excepción no controlada en su método Finalize, ya que eso terminará su proceso (en 2.0 o superior).
  • Evite realizar cualquier tarea de ejecución prolongada en el método de Finalizer, ya que eso bloqueará la secuencia del Finalizador y evitará que se ejecuten otros métodos de Finalizer.

Algunas pautas para la aplicación del método Dispose:

  • implementar el patrón de diseño de disponer de un tipo que encapsula los recursos que necesitan expresamente a ser liberados.
  • Implemente el patrón de diseño de disposición en un tipo base que tenga uno o más tipos derivados que se aferren a los recursos, incluso si el tipo base no.
  • Después de invocar el Deshacer en una instancia, evite que se ejecute el método Finalizar llamando al método GC.SuppressFinalize. La única excepción a esta regla es la rara situación en la que se debe trabajar en Finalize que no está cubierto por Dispose.
  • No asuma que se llamará a Dispose. Los recursos no administrados que son propiedad de un tipo también deben ser liberados en un método Finalize en caso de que no se llame a Dispose.
  • Lanza una excepción ObjectDisposedException de métodos de instancia en este tipo (que no sea Dispose) cuando los recursos ya están eliminados. Esta regla no se aplica al método Dispose porque debe llamarse varias veces sin lanzar una excepción.
  • Propaga las llamadas a Dispose a través de la jerarquía de tipos base. El método Dispose debe liberar todos los recursos que tenga este objeto y cualquier objeto propiedad de este.
  • Debería considerar no permitir que un objeto sea utilizable después de que se haya invocado su método Dispose. Volver a crear un objeto que ya ha sido eliminado es un patrón difícil de implementar.
  • Permitir que un método de eliminación sea invocado más de una vez sin lanzar una excepción. El método no debería hacer nada después de la primera llamada.
Cuestiones relacionadas