2011-10-04 17 views
13

He estado solucionando algunos problemas de pérdida de memoria en una aplicación de winforms y he notado algunos objetos desechables que no están explícitamente eliminados (el desarrollador no ha llamado método Dispose). La implementación del método Finalize tampoco ayuda porque no está incluida en la cláusula if (disposing). Todo el anulamiento de registro y eliminación de eventos estáticos se ha incluido en la cláusula if (disposing). La mejor práctica es invocar el Dispose si el objeto es desechable, pero lamentablemente esto sucede a vecesImplementación IDisposable: qué debe ir en 'si (eliminación)'

Si hay objetos no administrados, controladores de eventos estáticos y algunas colecciones administradas que deben borrarse al deshacerse. ¿Cuál es la manera de decidir qué debería entrar y qué debería salir de la cláusula if (disposing)?

Dispose method.

// Dispose(bool disposing) executes in two distinct scenarios. 
// If disposing equals true, the method has been called directly 
// or indirectly by a user's code. Managed and unmanaged resources 
// can be disposed. 
// If disposing equals false, the method has been called by the 
// runtime from inside the finalizer and you should not reference 
// other objects. Only unmanaged resources can be disposed. 
protected virtual void Dispose(bool disposing) 
{ 
    if (!disposed) 
    { 
     if (disposing) 
     { 
      // Free other state (managed objects). 
     } 

     // Free your own state (unmanaged objects). 
     // Set large fields to null. 
     disposed = true; 
    } 
} 

It says objetos gestionados debería en if (disposing) que ejecuta normalmente sólo cuando llamar explícitamente al método Dispose por el desarrollador. Si se ha implementado el método Finalize y el desarrollador se olvida de llamar al método Dispose, la ejecución que viene aquí a través del Finalizer no entra en la sección if (disposing).

A continuación se presentan mis preguntas.

  1. Si tengo controladores de eventos estáticos que causan pérdidas de memoria, ¿dónde debo anular el registro? ¿Dentro o fuera de la cláusula if (disposing)?

  2. Si tengo algunas colecciones que causan pérdidas de memoria, ¿dónde debo borrarlas? ¿Dentro o fuera de la cláusula if (disposing)?

  3. Si utilizo objetos desechables de terceros (p. Ej .: DevExpress winform controls) no estoy seguro de que sean objetos gestionados o no gestionados. Digamos que quiero eliminarlos cuando elimine un formulario. ¿Cómo puedo saber qué se gestionan y qué objetos no gestionados? Ser desechable no dice eso? En tales casos, ¿cómo decidir qué debería entrar y qué debería salir de la cláusula if (disposing)?

  4. Si no estoy seguro de que algo gestionados o no lo pueden ser las malas consecuencias de la eliminación/limpieza/anular el registro-eventos fuera de la cláusula if (disposing)? Digamos que comprueba nulo antes de deshacerse?

Editar

Lo que quiero decir como un evento Anular el registro es algo parecido a continuación. Publisher es una instancia de larga duración y la línea inferior está en el constructor del suscriptor. En este caso, el suscriptor debe anular el registro del evento y disponerlo ante el editor.

publisher.DoSomeEvent += subscriber.DoSomething; 

Respuesta

0

lo que debe ir en 'si (desechar)'

todos los objetos gestionados deben ir dentro de si la cláusula (desechar). Los objetos administrados no deberían salir a un lado (que se ejecutarán a través de la finalización).

La razón es que el proceso de finalización de los recolectores de basura puede ejecutar el Dispose (falso) si esa clase tiene un Destructor. Normalmente hay un Destructor solo si hay recursos no administrados. La finalización del recolector de basura no tiene un orden particular para ejecutar el método Finalizar. Por lo tanto, es posible que otros objetos administrados no estén en la memoria cuando se complete la tarea.

0

Si tengo controladores de eventos estáticos que causa pérdidas de memoria, donde debería eliminar el registro de ellos? ¿Dentro o fuera de la cláusula if (disposing)?

El método de eliminación se invoca en las instancias donde se usa el controlador de eventos estáticos en el nivel de clase. Por lo tanto, no debe anular su registro en absoluto para deshacerse de ellos. Por lo general, el controlador de eventos estáticos debe anular el registro cuando la clase está descargando o en algún momento durante la ejecución de la aplicación deriva que este controlador de eventos ya no es necesario.

Para todos los recursos administrados y no administrados, mejor implemente el patrón IDisposable. ve aquí http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx y Finalize/Dispose pattern in C#

+0

No es infrecuente que las instancias se suscriban a eventos estáticos. Tales instancias deben darse de baja dentro de la cláusula if (Disposing). – supercat

2

En términos generales, los recursos gestionados se disponen en el interior if (disposing) y no administrados los recursos fuera de ella. El patrón disponer funciona como tal:

  1. if (disposed) {

    Si este objeto ya está dispuesto, no disponer de él por segunda vez.

  2. if (disposing) {

    Si se solicitó la eliminación mediante programación (true), a disponer de recursos gestionados (objetos IDisposable) propiedad de este objeto.

    Si la eliminación fue causada por el recolector de basura (false), no elimine los recursos administrados porque el recolector de basura puede haber eliminado los recursos administrados y definitivamente los descartará antes de que la aplicación finalice.

  3. }

    Eliminar los recursos no administrados y liberar todas las referencias a ellos. El paso 1 asegura que esto solo suceda una vez.

  4. disposed = true

    Flag este objeto como dispuesto para evitar la eliminación repetida. eliminación repetida puede causar una NullReferenceException en el paso 2 o 3.

Pregunta 1
No se deshaga de ellos en el método Dispose en absoluto. ¿Qué pasaría si eliminaras varias instancias de la clase? Dispondrás de los miembros estáticos cada vez, a pesar de que ya estén eliminados. La solución que encontré fue manejar el evento AppDomain.DomainUnloaded y realizar la eliminación estática allí.

Pregunta 2
Todo depende si los elementos de la colección son administrados o no administrados. Probablemente valga la pena crear envoltorios administrados que implementen IDisposable para cualquier clase no administrada que esté utilizando, asegurando que todos los objetos se administren.

Pregunta 3
IDisposable es una interfaz administrada. Si una clase implementa IDisposable, es una clase administrada. Deseche los objetos gestionados dentro de if (disposing). Si no implementa IDisposable, está administrado y no requiere eliminación, o no está administrado y debe desecharse fuera de if (disposing).

Pregunta 4
Si la aplicación termina de forma inesperada, o no utiliza la eliminación manual, el recolector de basura dispone de todos los objetos en orden aleatorio.El objeto hijo puede desecharse antes de que se elimine a su padre, lo que hace que el niño sea eliminado una segunda vez por el padre. La mayoría de los objetos administrados se pueden desechar de manera segura varias veces, pero solo si se han construido correctamente. Se corre el riesgo (aunque poco probable) de que la colección de gargabe falle si un objeto se elimina varias veces.

+0

Un triturador nunca será llamado por el recolector de basura. Nunca. C# tiene un destructor (a.k.a a .Net Finalizer) que se llamará si y solo si el objeto es basura recolectada. – Spence

+0

La implementación de un destructor para un objeto es opcional. – Spence

+0

@Spence, tienes razón. El método 'Finalize' (yo uso VB) es opcional, pero es necesario si tiene recursos no administrados para deshacerse. Se usa como el punto de entrada del recolector de basura al método 'Dispose (Boolean)'. –

0

Parece que principalmente objetos han logrado es decir, objetos que implementan IDisposable. El código no administrado sería cosas como IntPtr's o handles, lo que normalmente significaría llamar al código no administrado o P/Invoke para llegar a ellos.

  1. Como Maheep señaló, Eliminar, no se entiende por esto. Cuando un objeto termina de recibir eventos, debe anular el registro. Si eso no es posible, considere usar WeakReferences en su lugar.

  2. Esto probablemente no debería ir en disponer a menos que estas colecciones contienen objetos que necesitan ser eliminados. Si son objetos desechables, debe ir en el bloque "si está desechando" y debe llamar a deshacerse de cada artículo en la colección.

  3. Si implementa IDisposable es administrado

  4. Usted no debe tener acceso a otros objetos de código administrado cuando se le llama por el finalizador que es lo que significa ser fuera de los "si (disposing)" medios de bloque.

3

La clave para recordar aquí es el propósito de IDisposable. Es el trabajo es ayudarle a forma determinista liberación de recursos que su código está llevando a cabo, antes de el objeto se recoge la basura. Esta es la razón por la cual el equipo de idioma de C# eligió la palabra clave using, ya que los corchetes determinan el alcance para el cual la aplicación requiere el objeto y sus recursos.

Por ejemplo, si se abre una conexión a una base de datos, que desea liberar esa conexión y cerrarla lo antes posible después de que haya terminado con él, en lugar de esperar a la próxima recolección de basura. Aquí es donde y por qué implementa un Disposer.

El segundo escenario es ayudar con código no administrado. Efectivamente, esto tiene que ver con las llamadas de la API de C++/C al sistema operativo, en cuyo caso usted es responsable de asegurarse de que no se filtre el código. Dado que gran parte de .Net se escribe simplemente en P/invocar a la API de Win32 existente, este escenario es bastante común. Cualquier objeto que encapsule un recurso del sistema operativo (por ejemplo, un Mutex) implementará un Disposer para permitirle liberar sus recursos de forma segura y determinista.

Sin embargo estas API también implementar un destructor, para garantizar que si no se utiliza correctamente el recurso de que no va a ser filtrado por el sistema operativo. Cuando su finalizador se llama, no se sabe si o no otros objetos que estabas hacer referencia a que ya se han recogido de basura, por lo que no es seguro para hacer llamadas de función sobre ellos (como podrían lanzar NullReferenceException), sólo las referencias no administrados (que por definición no puede ser basura recolectada) estará disponible para el finalizador.

Espero que ayude un poco.

+0

Saludos. Actualizado mi respuesta. – Spence

0

A menos que el único propósito de una clase sea encapsular algún recurso (*) que se debe limpiar si se abandona, no debe tener un finalizador, y Dispose (bool) nunca se debe invocar con un valor de False , pero llamar a Dispose (False) no debería tener ningún efecto.Si una clase heredada necesitara contener un recurso que requiera limpieza en caso de abandono, debería encapsular ese recurso en un objeto dedicado exclusivamente a ese fin. De esta forma, si el objeto principal se abandona y nadie más contiene ninguna referencia al objeto que encapsula el recurso, ese objeto puede realizar su limpieza sin tener que mantener el objeto principal vivo para un ciclo extra de GC.

Incidentalmente, no me gusta el manejo de Microsoft de la bandera Disposed. Sugeriría que el método Dispose no virtual debería usar un indicador entero con Interlocked.Exchange, para asegurar que llamar a Dispose desde múltiples hilos solo resultará en que la lógica de eliminación se realice una vez. La bandera en sí misma puede ser privada, pero debe haber una propiedad Dispuesta protegida y/o pública para evitar requerir que cada clase derivada implemente su propia bandera de disposición.

(*) Un recurso no es un tipo particular de entidad, sino un término suelto que abarca todo lo que una clase puede haber pedido a alguna entidad externa que haga en su nombre, que esa entidad externa necesita que le indique obra. Lo más habitual es que la entidad externa haya otorgado a la clase el uso exclusivo de algo (ya sea un área de memoria, un candado, un identificador GDI, un archivo, un zócalo, un dispositivo USB o lo que sea), pero en algunos casos el exterior Se le puede haber pedido a la entidad que haga algo afirmativamente (por ejemplo, ejecute un controlador de eventos cada vez que sucede algo) o que sostenga algo (por ejemplo, una referencia de objeto estático de hilo). Un recurso "no administrado" es uno que no se limpiará si se abandona.

BTW, tenga en cuenta que si bien Microsoft puede haber intentado que los objetos que encapsulan recursos no administrados deberían limpiarlos si se abandonan, existen algunos tipos de recursos para los que eso realmente no es práctico. Considere un objeto que almacena una referencia de objeto en un campo estático de subprocesos, por ejemplo, y borra esa referencia de objeto cuando está Dispose'd (la eliminación, naturalmente, tendría que ocurrir en el subproceso donde se creó el objeto). Si el objeto se abandona pero el hilo todavía existe (por ejemplo, en el grupo de hilos), el objetivo de la referencia de hilo estático podría mantenerse fácilmente indefinidamente. Incluso si no hay ninguna referencia al objeto abandonado por lo que se ejecuta su método Finalize(), sería difícil para el objeto abandonado ubicar y destruir la referencia estática de hilo situada en algún hilo.

Cuestiones relacionadas