2010-09-27 8 views
7

Tengo algunas preguntas básicas sobre el patrón de eliminación en C#.Preguntas específicas sobre C# Dispose Pattern

En el siguiente fragmento de código, que parece ser una forma estándar de implementar el patrón de disposición, observará que los recursos administrados no se manejan si la eliminación es falsa. ¿Cómo/cuándo se manejan? ¿El GC viene y maneja los recursos administrados más adelante? Pero si ese es el caso, ¿qué hace la llamada GG.SuppressFinalize (this)? ¿Puede alguien darme un ejemplo de deshacerse de los recursos administrados? Desenganchar eventos viene a la mente. ¿Algo más? La forma en que se escribe el patrón, parece que se eliminarán (más adelante) si no hiciste nada en la sección "si (deshacerse)". ¿Comentarios?

protected virtual void Dispose(bool disposing) 
{ 
    if (!disposed) 
    { 
     if (disposing) 
     { 
      // Dispose managed resources. 
     } 

     // There are no unmanaged resources to release, but 
     // if we add them, they need to be released here. 
    } 
    disposed = true; 

    // If it is available, make the call to the 
    // base class's Dispose(Boolean) method 
    base.Dispose(disposing); 
} 
// implements IDisposable 
public void Dispose() 
{ 
    Dispose(true); 
    GC.SuppressFinalize(this); 
} 

¿Es cierto lo que he leído acerca de los bloqueos en Dispose (bool) en este hilo, How do I implement the dispose pattern in c# when wrapping an Interop COM Object?? Dice: "Comentario de Meta-meta, además de eso, es importante que nunca adquieras bloqueos ni uses bloqueo durante tu limpieza no administrada". ¿Por qué? ¿Se aplica también a recursos no administrados?

Finalmente, ¿alguna vez implementa un finalizador (~ MyClass() en C#) sin implementar IDisposable? Creo que leí en alguna parte que los finalizadores y IDisposable no son necesarios (o deseables) si no hay recursos no administrados. Sin embargo, veo el uso de un finalizador sin IDisposable en algunos ejemplos (véase: http://www.codeproject.com/KB/cs/idisposable.aspx como un ejemplo) Gracias, de Dave

+0

¡Gracias por todas las buenas respuestas a todos! Solo puedo marcar uno como respuesta desafortunadamente. – Dave

Respuesta

5

Esta forma de implementar el patrón IDisposable es un modo a prueba de fallos: En caso de que un cliente olvida llamar al Dispose, el finalizador llamado por el tiempo de ejecución llamará al Dispose(false) más tarde (Tenga en cuenta que esta parte falta en su muestra).

En este último caso, es decir, cuando el finalizador llama al Dispose, los recursos gestionados ya se habrán limpiado porque, de lo contrario, el objeto en cuestión no habría sido elegible para la recolección de basura.

Pero si ese es el caso, ¿qué hace la llamada GC.SuppressFinalize (this)?

Ejecutar el finalizador tiene un costo adicional. Por lo tanto, se debe evitar si es posible. Si llama al GC.SuppressFinalize(this) se saltará la ejecución del finalizador y, por lo tanto, el objeto se puede recolectar de forma más eficiente.

En general, uno debe evitar confiar en los finalizadores ya que no hay garantía de que se ejecutará un finalizador. Algunos de los problemas con los finalizadores se describen por Raymond Chen en el siguiente mensaje:

When do I need to use GC.KeepAlive?

+2

Un pequeño detalle: * "En este último caso ... los recursos gestionados ya se habrán limpiado" *. Este no es necesariamente el caso: GC no es determinista por lo que tal vez se hayan limpiado, tal vez no. De cualquier manera, debe comportarse * como si * se hubieran limpiado (es decir, no debe intentar hacer nada con ellos). – LukeH

2

... Usted notará que los recursos administrados no son manejados si la eliminación es falso. ¿Cómo/cuándo se manejan?

No lo incluyó en su muestra, pero a menudo el tipo tendrá un destructor que llamará al Dispose(false). Por lo tanto, cuando disposing es false, que "saber" que estás en una llamada finalizador, y por lo tanto debe * * No acceder a los recursos gestionados, porque que puedan tener ya ha finalizado.

El proceso de finalización GC sólo asegura que los finalizadores se invocan, no el fin que están invocarse.

qué hace el GG.SuppressFinalize (este) llama a hacer?

Previene la GC de la adición de su objeto a la cola de finalización y, finalmente, llamando object.Finalize() (es decir, el destructor). Es una optimización del rendimiento, nada más.

La forma en que el patrón está escrito, parece que obtendrían dispuesto (más tarde) si usted no hizo nada en la sección “si (desechar)”

Tal; depende de cómo se escribe el tipo. Un punto importante de la expresión IDisposable es para "finalización determinista" - diciendo "Quiero que tus recursos se liberen ahora" y que signifique algo. Si "ignorar" el bloque disposing=true y no lo hace "hacia adelante" la Dispose() llamada, una de dos cosas sucederá:

  1. Si el tipo tiene un finalizador, el finalizador para el objeto con el tiempo puede ser invocado en algún momento " luego".
  2. Si el tipo no tiene un finalizador, el recurso administrado se "fugará", ya que Dispose() nunca se invocará en ellos.

es importante que nunca se adquieren bloqueos o el uso de bloqueo durante su limpieza no administrado.”¿Por qué? ¿Se aplica también a recursos no administrados?

Es un problema de cordura: SU cordura. Cuanto más simple sea su código de limpieza, mejor, y es siempre una buena idea para no lanzar excepciones desde Dispose(). El uso de bloqueos puede provocar excepciones o bloqueos, que son buenas maneras de arruinar tu día. :-)

¿En vez implementar un finalizador (~ MiClase() en C#) sin implementar IDisposable

Se podría, pero sería considerado un mal estilo.

1

La manera normal de eliminar los objetos es llamando al método Dispose(). Cuando se hace de esa manera, la llamada SuppressFinalize elimina el objeto de la cola del finalizador, convirtiéndolo en un objeto administrado regular que puede ser fácilmente recolectado como basura.

El finalizador solo se utiliza cuando el código no puede eliminar el objeto correctamente. Luego, el finalizador llama al Dispose(false) para que el objeto al menos intente limpiar recursos no administrados. Como los objetos gestionados a los que hace referencia el objeto pueden haber sido recolectados en este momento, el objeto no debería intentar limpiarlos.

0

En lugar de tratar de aprender sobre la disposición a través del patrón, es posible que desee cambiar las cosas y tratar de aprender por qué el patrón se implementa de esta manera en base a los fundamentos CLR y el uso previsto de la interfaz IDisposable. Hay una muy buena introducción a esto que debería responder todas sus preguntas (y algunas que no pensó preguntar) al http://msdn.microsoft.com/en-us/magazine/cc163392.aspx.

3

El patrón descrito anteriormente es una cuestión de tratar elocuentemente con las preocupaciones superpuestas de eliminación y finalización.

Cuando estamos disponiendo, queremos:

  1. Desechar todos los objetos miembro desechables.
  2. Deseche el objeto base.
  3. Libere recursos no administrados.

Al finalizar queremos:

  1. liberar recursos no administrados.

A esto se suman las siguientes preocupaciones:

  1. La eliminación debe ser seguro llamar varias veces. No debería ser un error llamar al x.Dispose();x.Dispose();
  2. La finalización agrega una carga a la recolección de basura. Si lo evitamos si podemos, específicamente si ya hemos lanzado recursos no administrados, queremos suprimir la finalización ya que ya no es necesaria.
  3. El acceso a los objetos finalizados es precario. Si un objeto se está finalizando, entonces todos los miembros finalizables (que también estarían lidiando con las mismas preocupaciones que nuestra clase) pueden o no haberse finalizado y estarán definitivamente en la cola de finalización. Como estos objetos probablemente también serán objetos desechables administrados, y al deshacerse de ellos liberará sus recursos no administrados, no queremos deshacernos de ellos en ese caso.

El código que usted proporciona (una vez que se agrega en el finaliser que llama Dispose(false) gestionar estas preocupaciones. En el caso de Dispose() se llamó va a limpiar ambos miembros administrados y no administrados y suprimir la finalización, y al mismo tiempo la protección contra múltiples llamadas (no obstante, no es seguro para subprocesos). En el caso de que se llame al finalizador, se eliminarán los miembros no administrados.

Sin embargo, este patrón solo es requerido por el patrón anti de combinar administrado y preocupaciones no administradas en la misma clase. Un enfoque mucho mejor es manejar todos los recursos no administrados a través de una clase que solo se ocupa de ese recurso, ya sea SafeHandle o una clase separada tuyo. A continuación, tendrá uno de dos patrones, de los cuales este último será raro:

public class HasManagedMembers : IDisposable 
{ 
    /* more stuff here */ 
    public void Dispose() 
    { 
     //if really necessary, block multiple calls by storing a boolean, but generally this won't be needed. 
     someMember.Dispose(); /*etc.*/ 
    } 
} 

Esto no tiene finaliser y no necesita uno.

public class HasUnmanagedResource : IDisposable 
{ 
    IntPtr _someRawHandle; 
    /* real code using _someRawHandle*/ 
    private void CleanUp() 
    { 
    /* code to clean up the handle */ 
    } 
    public void Dispose() 
    { 
    CleanUp(); 
    GC.SuppressFinalize(this); 
    } 
    ~HasUnmanagedResource() 
    { 
    CleanUp(); 
    } 
} 

Esta versión, que será mucho más raro (ni siquiera ocurre en la mayoría de los proyectos) tiene la oferta disposición exclusivamente con el trato con el único recurso no administrado, por el que la clase es un contenedor, y el finaliser haciendo lo mismo si la eliminación no sucedió.

Dado que SafeHandle permite que se maneje el segundo patrón, realmente no debería necesitarlo. En cualquier caso, el primer ejemplo que proporciono manejará la gran mayoría de los casos en los que necesite implementar IDisposable. El patrón dado en su ejemplo solo debe usarse para compatibilidad con versiones anteriores, como cuando se deriva de una clase que lo usa.

0

Si su clase retiene recursos no administrados directamente, o si alguna vez podría ser heredada por una clase descendiente que lo haga, el patrón de disposición de Microsoft proporcionará una buena manera de vincular el finalizador y el triturador. Si no hay una posibilidad real de que su clase o sus descendientes retendrán recursos no administrados directamente, debe eliminar el código de la plantilla y simplemente implementar Eliminar directamente. Dado que Microsoft recomendó encarecidamente que los recursos no administrados se envuelvan en clases cuyo único propósito es mantenerlos (*) (y tiene clases como SafeHandle precisamente para ese propósito) ya no hay necesidad de usar el código de la plantilla.

(*) La recolección de basura en .net es un proceso de varios pasos; primero, el sistema determina a qué objetos no se hace referencia en ningún lugar; luego hace una lista de objetos Finalizables que no están referenciados en ninguna parte. La lista y todos los objetos en ella se volverán a declarar "en vivo", lo que significa que todos los objetos a los que se refieren también estarán en vivo. En ese punto, el sistema realizará una recolección de basura real; luego ejecutará todos los finalizadores en la lista. Si un objeto se mantiene, p. un identificador directo a un recurso de fuente (no administrado), así como referencias a otros diez objetos que a su vez contienen referencias directas o indirectas a un centenar de objetos más, y debido al recurso no administrado, el objeto necesitará un finalizador. Cuando el objeto vence para la recopilación, ni él ni los más de 100 objetos a los que tiene referencias directas o indirectas serán elegibles para la recopilación hasta el pase después de que se ejecute su finalizador.

Si en lugar de mantener un identificador directo al recurso fuente, el objeto contiene una referencia a un objeto que contiene el recurso fuente (y nada más), este último necesitaría un finalizador, pero el anterior no (ya que no contiene una referencia directa al recurso no administrado. Sólo un objeto (el que tenía el finalizador), en lugar de 100+, tendría que sobrevivir a la primera recolección de elementos no utilizados

5

Nadie llegó al las últimas dos preguntas (por cierto, solo pregunte una por hilo). Usar un candado en Dispose() es bastante letal para el hilo del finalizador. No hay un límite superior de cuánto tiempo se puede retener el bloqueo, su programa se bloqueará después de dos segundos cuando el CLR nota que el hilo del finalizador se atascó. Por otra parte, es solo un error. Nunca debe llamar a Dispose() cuando otro hilo aún podría tener una referencia al objeto.

Sí, implementar un finalizador sin implementar IDisposable no es algo inaudito. Cualquier contenedor de objetos COM (RCW) hace eso. Lo mismo ocurre con la clase Thread. Esto se hizo porque no es práctico llamar a Dispose(). En el caso de un contenedor COM porque simplemente no es posible realizar un seguimiento de todos los recuentos de referencia. En el caso de Thread porque tener que Join() el hilo para que pueda llamar a Dispose() vence el propósito de tener un hilo.

Preste atención a la publicación de Jon Hanna. La implementación de su propio finalizador está mal el 99.99% del tiempo. Tienes las clases SafeHandle para envolver los recursos no administrados. Necesitarías algo bastante oscuro para no ser envuelto por ellos.

+0

Los hilos gestionados se suspenden durante la recolección de basura, pero no creo que se suspendan durante la finalización. La forma en que funciona la finalización, los objetos para los que se habilita la finalización y los objetos a los que se refieren directa o indirectamente, no se recolectarán como basura, pero si son elegibles para la recolección de basura se finalizarán una vez que se recolecte la basura. el pase está completo. Una vez que se ejecuten los finalizadores, a menos que los objetos se vuelvan a registrar para la finalización, ya no serán elegibles para la finalización y se recogerán en el siguiente gc. – supercat

+0

@super - tienes razón, no estoy seguro de por qué escribí eso. Daño deshecho, gracias. –

+0

@Hans: creo que la clase Thread utiliza finalizadores para limpiar la asignación de identificadores de subprocesos administrados. Todos los objetos Thread creados por separado que existen deben tener una identificación distinta; dado que los identificadores de subprocesos son solo de 32 bits, el marco debe ser capaz de reutilizarlos (de lo contrario, cualquier programa que creara dos mil millones de objetos de subprocesos durante su ciclo de vida). Como pueden ocurrir cosas malas si dos objetos Thread tienen el mismo ManagedThreadID, los finalizadores se utilizan para garantizar que los ID solo estén disponibles para su reutilización cuando no haya más referencias a los objetos Thread que los contienen. – supercat