¿Aquí quíen #@$@#~€ barre? … o lo mínimo que todo desarrollador debe saber sobre IDisposable y los finalizadores

  Durante mucho tiempo, mi cuadrilla, disfruto de un chamizo donde nos reuníamos para fumar, beber, comer, ver cine, jugar a cartas y otras cuestiones menos confesables… fueron años divertidos. El chamizo siempre planteaba la misma duda… ¿a quien coño le toca barrer?  La gente, ya se sabe, a la menor ocasión, se escudaba en las falta de reglas claras y la consecuencia es que nadie barría… al final la acumulación de colillas, cascos de cervezas y en general residuos no orgánicos de todo tipo hacían que el chamizo colapsase y tuviésemos que dedicar un día completo a limpiarlo… con el consiguiente mosqueo… hartos de la situación establecimos unas normas claras y nunca más volvió ha haber problemas.


 


Lo mismo que nos pasaba a nosotros le pasa a veces a .Net… el pobre recolector de basura y la pobre memoria a veces dicen basta por que muchos desarrolladores de .Net desconocen algunas normas y patrones claros de cómo se debe implementar IDisposable. En este post voy a comentar esas normas… sin justificarlas. Solo quiero exponer una serie de normas que si se respetan nos ayudaran a evitar que nuestras aplicaciones tenga problemas en lo relativo a la memoria por no liberar recursos de manera adecuada. También quiero dejar claro que es un mito que las aplicaciones de .Net no puedan fugar memoria. Es más díficil que esto ocurra que cuando desarrollamos en leguajes no manejados pero puede ocurrir y evidentemente siempre pueden ocurrir consumos de memoria elevados por no liberar a tiempo los recursos.


 


Vayamos al grano de este post y contestemos a la pregunta clave que hoy abordaremos: ¿Cuándo y cómo el desarrollador debe coger la escoba e implementar IDisposable o un finalizador?


 


Simplificando mucho el asunto: el recolector de basura pasa cuando quiere. Si nuestra clase mantiene estructuras que consumen mucha memoria o que representan recursos limitados del sistema operativo (habitualmente representados mediante handles), nos puede interesar decirle al mundo que deberian liberarse cuanto antes. La manera de lanzar ese mensaje al mundo es implementar IDisposable.  Cuando uno de nuestros tipos implementa IDisposable todos los programadores de .Net del mundo (o mejor del universo) reciben un mensaje claro: esta clase contiene recursos valiosos que deben ser liberados cuanto antes, no debemos esperar a que el recolector de basura actúe. Para ello el típico patrón es usar las clases que implementan IDisposable mediante using. IDisposable NO es llamado por el recolector de basura, nosotros debemos hacerlo, bien usando using, que se encargará de llamar a Dispose cuanto sale de ambito o llamando explícitamente al método Dispose del objeto. Resumiendo: Llamar a Dispose nos permite liberar recursos valiosos de manera temprana.


 


Ocurre a menudo, que los recursos valiosos de los que hablo además son recursos no manejados. Los recursos no manejados no son liberados por el recolector de basura. Nosotros como desarrolladores somos los encargados de liberarlos. La manera de liberar esos recursos es implementando un finalizador. Los finalizadores SI son llamados por el recolector de basura cuando limpian un objeto de memoria.  Resumiendo: Los finalizadores nos permiten asegurar que nuestro código de limpieza de recursos no manejados, escrito en el finalizador, será ejecutado cuando el recolector de basura libere nuestro objeto. Pero este momento se puede demorar mucho.


 


Generalmente IDisposable y los finalizadores van de la mano, por la cuestión ya comentada de que los recursos no manejados suelen ser recursos valiosos que, en consecuencia, conviene liberar lo antes posible.


 


Ahora que ya conocemos cuándo debemos implementar los finalizadores e IDisposable vamos a ver como implementarlos. La manera en la que se implementan es siguiendo el patrón IDisposable que tiene la siguiente forma (por favor leed los comentarios):


 



    1 public class DisposableClass : IDisposable


    2 {


    3   //Finalizador.


    4   //Llamado por el GC al eliminar el objeto de memoria.


    5   ~DisposableClass()


    6   {


    7     //Liberamos los recursos no manejados


    8     Dispose(false);


    9   }


   10 


   11   //Llamado explicitamente por el desarrollador.


   12   //bien usando using o invocandolo explicitamente.


   13   public void Dispose()


   14   {


   15     //Llamamos a Dispose para liberar los recursos mantenidos por la clase


   16     Dispose(true);


   17     //Puesto que ya hemos realizado la liberación de recursos, no es


   18     //necesario que el GC llame al finalizador


   19     System.GC.SuppressFinalize(this);


   20   }


   21 


   22   //Esta función aglutina todo el proceso de liberación de recursos.


   23   //Se invoca desde el finalizador pasando false, pues el finalizador solo


   24   //debe liberar recursos no manejados.


   25   //Se invoca desde el método Dispose pasando true pues queremos en ese caso


   26   //liberar todos los recursos valisos agregados por el objeto, sena estos


   27   //manejados o no.


   28   protected virtual void Dispose(bool disposing)


   29   {


   30     if (disposing)


   31     {


   32       //Liberar aquí los objetos manejados agregados en esta clase


   33       //que implementen IDisposable llamando explicitamente a su método Dispose.


   34     }


   35 


   36     //Liberar aquí todos los recursos no manejados.


   37   }


   38 }


 


He prentendido aquí, comentar las pautas generales que todo desarrollador debe como mínimo conocer sobre este tema y que nos sirven como guia general. Hay situaciones complejas, como el uso de liberías no manejadas usando DllImport que exigen más atenciones y consideraciones. Además no he entrado en el porque de las cosas o en detalles de la recoloección de memoria que son interesantes. ¡Solo quería cubrir lo mínimo que todo el mundo debe saber! Seguro que algunos de vosotros querréis indagar más sobre el tema. Si es así, os recomiendo la lectura de DG Update- Dispose, Finalization, and Resource Management de Joe Duffy. Excelente artículo sobre el tema adornado con anotaciones de gente del nivel de Jeffrey Ritcher, Herb Sutter, Krzysztof Cwalina…


 


¡Un saludo!

15 comentarios en “¿Aquí quíen #@$@#~€ barre? … o lo mínimo que todo desarrollador debe saber sobre IDisposable y los finalizadores”

  1. Excelente artículo … 😀

    Y la frase “Simplificando mucho el asunto: el recolector de basura pasa cuando quiere. ” me ha hecho reir solo un buen rato … jejeje

    Saludos

  2. Y luego dicen que el .NET es más fácil que C++… 😛

    Joer, Rodri, clarísima y quirúrgica exposición sobre el tema. Ya podrían algunos libros de gurús explicarlo así. Moooooooooola.

  3. @Bruno: a veces simplicar el tema al absurdo sirve. Espero que nadie se quede con la idea erronea de que el GC no hace bien su trabajo… 🙂

    @Rafael: Hombre, un poco más sencillo ya es… prefiero tener que recordar algunas reglas claras que tener que acordarme de liberar todo lo que asigno… pero vamos, que si una cosa buena tiene C/C++ es que te enseña que la memoria es limitada y que hay que ser cuidadoso con ella.

    ¡Saludos a ambos y gracias por los comentarios!

  4. Rodri,

    Excelente post, siempre es bueno recordar estas cosas incluso si (crees que ) las sabes :-).
    No has hablado explícitamente de cómo implementar el patrón en presencia de herencia (o sea, si la clase que estás definiendo ya hereda de otra que es IDisposable). Podría deducirse de lo que dices, pero no es del todo evidente.

    Aquí está la info al respecto:

    http://msdn.microsoft.com/es-es/library/b1yfkh5e.aspx

    Abrazo – Octavio

  5. Hola Octavio!!!
    Precisamente tenía idea de completar el post con lo que tu comentas en otro post posterior. Pero me has ahorrado el trabajo…

    ¡Un saludo!

  6. Gracias a Dios un poco de luz entre tanta oscuridad, aunque tengo que decirte que en algunos articulos recomiendan implementar IDisposable solo en el caso de tener recursos no majedos como dll externas o similares, y dejar para el recolector todo lo demas, en cuaquier caso estoy mucho mas deacuerdo con lo que explicas en tu post.

  7. @Octavio: No te preocupes Octavio, lo importante es que la información llegue.

    @Juan: Hay mucha oscuridad sobre este tema… y mucha información erronea en internet. Lo que yo cuento en este post, he tratado de asegurar que era correcto.

    ¡Un saludo!

  8. Hola Rodri, sobre el tema del recolector de basura que pasa cuando quiera!!, según tengo entendido cuando se llena la generación 0 y cuando se va a crear otro objeto, no cuando la generación está llena sino cuando reservamos memoria el GC recolecta la basura, normalmente el tamaño de la generación 0 es igual a la cache de 2 nivel de procesador aunque esto puede variar según versión del clr (desktop, server o concurrent).

  9. @Luis: trataba de hacer algo que todo el mundo pueda entender con facilidad, por eso he simplificado cuanto he podido el asunto. Claro que el recolector no pasa cuando quiere.

    Pero si piensas en que no es determinista, vas a tener menos problemas que si crees que pasa cada cierto tiempo o cuando hace falta o lo que sea. En resumen si no sabes exactamente como funciona, lo mejor es asumir que pasa cuando le da la gana, así no te equivocas ;).

    En cualquier caso, te agradezco el comentario que enriquece mucho el post. No es malo que la gente sepa como funciona el tema.

    ¡Un abrazo!

  10. Lo peor de todo es que aún sabiendo como funciona el GC y porque y para que existe IDisposable, algunos catetos como YO no lo implementamos la mayoria de las veces.
    Aunque siempre podemos decir que es “optimización temprana” y nos quedamos tan agusto. (¿de quien aprendería yo ese palabro!?)
    Saludos,Pedro

  11. Jajjajaja… Pedro… en este caso es más una buena práctica que una optimización temprana!!! Se que lo sabes pero no sea que alguien lea tu comentario y se piense lo que no es!!!

    ¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *