Gustavo さんのプロフィールGustavo Bonanseaブログリスト ツール ヘルプ
    10月24日

    Patrón Dispose

    Hace unos días recibí un mail de Luigi desde Lima, Perú; preguntando sobre el uso del Patrón Dispose, dado que se enteró del seminario que estaba por dar; al cual no iba a poder asistir debido a las distancias. Para contestar a Luigi y para complementar lo que expuse en el seminario es que me tomo la libertad de escribir algunas líneas sobre el tema y les paso un link sobre el artículo que considero más completo sobre el tema (además de los del MSDN, por supuesto).
     
    Patrón Dispose:
     
    El CLR puede disponer enteramente de todos los objetos manejados, esto quiere decir que es su tarea crearlos y destruirlos; es el DIOS de los objetos manejados. El garbage collector puede disponer del espacio de memoria que ocupa el objeto cuando piense que nuestra aplicación no lo seguirá utilizando, puede mover el objeto de posición en caso de creerlo necesario (de hecho después de cada recolección se realiza una deframentación del montón que implica mover los objetos para que queden adyacentes), etc.
    Pero, que pasa si utilizamos un recurso que se encuentra fue de los límites del CLR. Supongamos que nos queremos conectar remotamente con algún dispositivo especial y las librerías para conectarse con el mismo se encuentran publicadas en una dll que no es .Net. ¿Cómo podemos asegurarnos de que se liberará el recurso una vez que no se necesite más? El CLR no puede hacerlo automáticamente dado que no tiene conocimiento sobre qué métodos tiene la dll y para qué sirve cada uno; por lo tanto no podrá cerrarlo por sí mismo. Para paliar esta situación podemos utilizar el Patrón Dispose. Este patrón define una función especial llamada Dispose que sirve para que el usuario (entiéndase por "usuario" a cualquier objeto que utilice la clase, no a una persona física que ejecute el programa) la llame explícitamente para liberar los recursos no manejados (en este ejemplo la conexión con el dispositivo), por lo tanto en el cuerpo de este método (Dispose) deberemos hacer una llamada a la función no manejada que libera los recursos del dispositivo. Quiero aclarar en este punto que no basta con que el usuario de la clase que contiene el recurso no manejado haga nulo el objeto, eso solo libera (lo deja en manos del Garbage Collector en realidad) la parte manejada del mismo, pero no llama al método necesario para liberar el recurso no manejado, es decir que no se llama al método de la dll que lo cierra, por eso debe llamar explícitamente al método Dispose.
    En el escenario ideal el usuario de la clase llamará al método Dispose inmediatamente después de haber terminado de usarla, liberando así el recurso para que sea utilizado por otro objeto. ¿Qué pasa si el programador olvida llamar al método Dispose? Desgraciadamente la respuesta es que el recurso no se liberará. Por ello el patrón Dispose incluye una implementación del método Finalize. Éste método tiene la característica especial de que se llama automáticamente por el CLR antes de que el objeto sea destruido, por lo tanto aunque el programador olvide llamar explícitamente al Dispose éste se llamará automáticamente cuando el objeto sea destruido por el Garbage Collector. Cabe aclarar que éste es un mecanismo de emergencia, el programador DEBE llamar explícitamente al método Dispose, solo en caso de una omisión debe dejarse en manos del finalizador esta tarea. ¿Por qué? Porque no tenemos control de cuando se llamará el método Finalize, ésto depende de que se ejecute el Garbage Collector y éste le informe al thread de finalización que debe llamar al método. Si los requerimientos de memoria de la aplicación a partir del momento en que se deja de utilizar el objeto no le aplican demasiada presión el GC no correrá y la conexión con el dispositivo puede pasar mucho tiempo si cerrarse (en el caso extremo puede ser hasta que se termine el programa).
     
    Quiero aclarar que el uso del método Dispose solo debe utilizarse para el caso de necesitar la liberación de un recurso no manejado y los usuarios de la clase que implementan el método deben llamarlo en forma explícita, el finalizador debe incluirse en esa clase como medida preventiva en caso de la omisión de la llamada al Dispose por parte del código que utiliza la clase.
     
    Ejemplo (extraído del documento de Joe Duffy que se referencia debajo. Es un poco más completo que el de mi seminario)
     

    public class ComplexCleanupBase : IDisposable

    {

     

        // some fields that require cleanup

        private bool disposed = false; // to detect redundant calls

     

        public ComplexCleanupBase()

        {

            // allocate resources
        }

     

        protected virtual void Dispose(bool disposing)

        {

            if (!disposed)

            {

                if (disposing)

                {

                    // dispose-only, i.e. non-finalizable logic

                }

     

                // shared cleanup logic

                disposed = true;

            }

        }

     

        ~ComplexCleanupBase()

        {

            Dispose(false);

        }

     

        public void Dispose()

        {

            Dispose(true);

            GC.SuppressFinalize(this);

        }

     

    }

     

    Algunas consideraciones sobre el ejemplo:

    • IDisposable: Para identificar los objetos que poseen el mecanismo de Dispose debe implementarse la interfaz IDisposable. De esta forma podemos saber si una clase necesita ser liberada llamado a Dispose averiguando si implementa esta interfaz
    • disposed: variable que se utiliza para saber si ya se ha llamado al método Dipose con anterioridad. En caso de ser así no se realiza la liberación del recurso otra vez
    • disposing: variable que se utiliza para saber si el método Dispose se está llamando internamente desde el finalizador de la clase o por el usuario. Nótese que el método que llama a Dispose con un parámetro es protected, por lo tanto el usuario solo puede llamar al Dispose sin parámetros (el cual llama al otro pasándole true). El finalizador llama al método Dispose con parámetros pasándole false.
    • ~ComplexCleanupBase(): Finalizador, llama a Dispose(false). Se ejecuta en caso de que el usuario del objeto olvide llamar explícitamente a Dispose
    • public void Dispose(): Método público que es ejecutado por los usuarios del objeto para liberar los recursos. Llama a Dispose(true), luego llama al método GC.SuppressFinalize, lo cual le indica al Garbage Collector que no necesita finalizar el objeto porque el usuario ya liberó los recursos.
    • protected virtual void Dispose(bool disposing): Método protegido que realiza la tarea de liberación de recursos. Si la variable disposed es true indica que ya se liberaron los recursos y no realiza nada. Luego, si el valor de disposing es true indica que el método fue llamado por el usuario y realiza toda la lógica de liberación del código manejado y no manejado. En caso de ser false quiere decir que se llamó dentro del finalizador y como en .Net no se garantiza el orden en el cual se llaman los finalizadores no puede realizarse ninguna tarea con los objetos manejados internos de la clase dado que pueden haber sido finalizados antes.
     
    Espero que haya sido de ayuda.
     

    GB

     

    コメント (10 件)

    しばらくお待ちください。
    入力されたコメントは長すぎます。短くしてください。
    何も入力されていません。もう一度やり直してください。
    現在、コメントを追加できません。後でもう一度やり直してください。
    コメントと書くには、保護者 (ほごしゃ) の方の許可 (きょか) をもらってください。許可をリクエストする
    保護者 (ほごしゃ) の方が、あなたがコメントを書けないようにしています。
    現在、コメントを削除できません。後でもう一度やり直してください。
    1 日に投稿できるコメントの最大数を超えました。24 時間経過してから、もう一度やり直してください。
    あなたが他のユーザーに対して迷惑行為を行っている可能性があると確認されたため、お使いのアカウントによるコメントの投稿を無効にしています。誤って無効にされたと思われる場合は、Windows Live のサポートにお問い合わせください。
    コメントを投稿する前に、以下のセキュリティ チェックを完了してください。
    セキュリティ チェックに入力する文字は、画像に表示されている文字または音声で流れた文字と一致していなければいけません。

    コメントを投稿するには、お使いの Windows Live ID でサインインしてください (Hotmail、Messenger、または Xbox LIVE を既に使用している場合は、そのアカウントが Windows Live ID です)。サインイン


    Windows Live ID をお持ちでない場合は、アカウントを新規登録してください。

    Bonansea Gustavoさんの投稿:
    Le doy unos mangos a don Google para que me posicione bien :D
    5 月 11 日
    匿名 の表示アイコン
    Isa さんの投稿:
    Ahhh hno... podes creer q me salio en segundo lugar esta pagina en una busqueda en el google... por cierto muy util.. ya te voy a hacer unas consultitas
    4 月 19 日
    Bonansea Gustavoさんの投稿:
    La clase BindingManagerBase (y sus heredadas "CurrencyManager" y "PropertyManager") no tiene ningún método Dispose o Close, por lo tanto lo único que puedes hacer es igualarla a null apenas termines de utilizarla y esperar que el Garbage Collector la destruya.
     
    Saludos.
    6 月 12 日
    匿名 の表示アイコン
    Yiyouuuuu2 さんの投稿:
    Como puedo liberar una variable binding manager base en vb.net 2005????
    6 月 8 日
    Bonansea Gustavoさんの投稿:
    Me alegro sinceramente haberte servido de ayuda, para eso estamos en esta comunidad virtual, para aprender y mejorar todos juntos.
    Gracias por los buenos augurios,
     
    Nos leemos.
    2 月 6 日
    匿名 の表示アイコン
    Daniel さんの投稿:
    Hola. Aquí estoy denuevo para agradecerte tu clara respuesta. Se entendió todo a la perfección y al fin me quité esa duda que venía arrastrando hace tiempo y nadie sabia ayudarme.
    "SIEMPRE llama a Close. De última si no me crees... mal no le va a hacer, es solo una línea más de código"  jaja claro que te creo... y vos creeme que tu palabra para mi se antepone a lo que cualquier manual de Microsoft me pueda decir =P.
    En fin, te agradezco toda la ayuda prestada, y espero muy pronto estar posteando nuevamente.
    Te mando saludos.
     
    P.D.: felicitaciones por la futura mudanza =)
    2 月 5 日
    Bonansea Gustavoさんの投稿:
    Daniel, primero que nada, no me molesta en lo absoluto que me realicen preguntas; de hecho es el objetivo de este blog, que todos discutamos sobre temas que nos parezcan interesantes.
     
    Con respecto a tus preguntas.
    1- "Lo primer duda que me surge es si luego de cerrar la conexión es necesario igualar el objeto a null"
    Este es un tema delicado..... La mayoría de la documentación sobre el framework .Net hace referencia a que esto es innecesario, me refiero a la igualación a null. El tema de la llamada a Close, es diferente porque aunque de todas formas el GC tarde o temprano libera la memoria, es altamente recomendable hacerlo lo más rápido posible, sobre todo para liberar los recursos no manejados. Volviendo al tema del null, teóricamente cuando la variable sale de alcance (es decir que se termina el bloque en el que se encuentra declarada) automáticamente queda a disposición de GC para ser liberada. Pero, en mi experiencia personal he encontrado diferencias (grandes en sistemas muy complejos) entre dejarlo en manos del destino o hacerlo explícitamente. Esto es más evidente en aplicaciones que son grandes, poseen muchas variables o propiedades públicas que pueden ser referenciadas desde otras partes por varios objetos, etc.
    Por lo tanto mi recomendación es la siguiente:
    - Si el objeto implementa la interfaz IDisposable: Llamar explícitamente al Dispose inmediatamente después de dejar de utilizarlo (o utilizar la palabra clave using en C#)
    - Si el objeto no es IDisposable; pero tiene algún método Close() o similar: Llamar explícitamente al Close inmediatamente después de dejar de utilizarlo
    - Si el objeto no tiene nada de lo anterior: Igualarlo a null apenas dejamos de usarlo. (Especialmente para tipos de datos como xml, cadenas, arrays, collecciones, etc.)
    2-"Por otro lado me llama la atención que el objeto tipo DataSet no usa Close() y es igualado directamente"
    SIEMPRE llama a Close. De última si no me crees... mal no le va a hacer, es solo una línea más de código :D
     
    Saludos.-
    2 月 3 日
    匿名 の表示アイコン
    Daniel さんの投稿:
    Hola Gustavo, nuevamente me doy una vuelta por tu blog, en un principio para agradecerte la respuesta que me ha sido de suma utilidad, y también para felicitarte por el muy buen artículo que has escrito y gentilmente me has enviado ya que me ha aclarado unos cuantos puntos importantes.
     
    Como de costumbre, me ha surgido una duda con una particularidad que ya había notado en un tutorial de Microsoft y que ahora veo repetida en tu artículo. Ellos hacen algo así:
     
    SqlConnection con=new SqlConnection("...");
    DataSet dSet=new DataSet();
    /* ... */
    // Libero recursos
    con.Close();
    con=null;
    dSet=null;
     
    Lo primer duda que me surge es si luego de cerrar la conexión es necesario igualar el objeto a null. Entiendo que al ponerlo en null el objeto pasa a disposición del GC, pero yo suponía que al aplicarle el con.Close() ya no era necesario el resto.
    Por otro lado me llama la atención que el objeto tipo DataSet no usa Close() y es igualado directamente.
    Realmente al ver ese ejemplo quedé algo confundido. No se cuando usar Close y cuando igualar a null directamente sin el Close previo.
     
    Bueno, espero no estar desesperando tu paciencia con mis "cuestionamientos", y también espero que mis próximos posts puedan ser aportes en lugar de preguntas.
     
    Una vez mas agradezco mucho tu atención prestada en la anterior respuesta y por haberte tomado la molestia de enviarme tu muy buen artículo.
    Te dejo saludos.
    2 月 2 日
    Bonansea Gustavoさんの投稿:
    Hola Daniel, mucho gusto en conocerte. Me alegra haberte sido útil en algo.
    Si el objeto posee internamente otros que implementan la interfaz IDisposable deben llamar a su dispose (el de los objetos internos) cuando se ejecute el dipose del objeto.
    En el caso de un Dataset (DataTable, etc.) debes llamar el método Close() para eliminar los recursos.
    En el caso de cualquier otro objeto que no implemente la interfaz, como por ejemplo ArrayList (los String también caen dentro de este grupo) lo único que puedes hacer (y de hecho yo recomiendo ampliamente por experiencia personal) es igualar la referencia a Nothing para que el objeto quede inmediatamente a disposición del GC, pero en todos casos siempre dependes de la ejecución del GC para que se libere totalmente la memoria. Lo que estamos haciendo es hacerle más fácil y rápido su trabajo.
    Te voy a mandar por mail un artículo que escribí sobre el funcionamiento del GC para que lo leas, quizás te sirva para aclarar un poco más las cosas. De todas formas podemos seguir discutiendo sobre el tema en este espacio.
     
    Saludos
    1 月 31 日
    匿名 の表示アイコン
    Daniel さんの投稿:
    Hola Gustavo, mi nombre es Daniel y he llegado a tu (muy útil) blog a través de Google intentando informarme acerca de la utilización del Dipose.
     
    Te comento que lo que has escrito me resulto de suma utilidad, ya que, con mis conocimientos, no soy capaz de comprender los artículos de Micrsoft que, para mi gusto, explican todo menos lo que me interesa saber (son poco claros).
    Al leer lo que has colocado aquí, me surge una gran duda: si nosotros declaramos dentro de ComplexCleanupBase un objeto IDisposable, liberaríamos su memoria tranquilamente con la utilización de objeto.Dispose(); pero ¿cómo tendríamos que hacer para liberar la memoria de un objeto que NO incorpora la interfaz IDisposable (como por ejemplo un objeto DataSet) y también liberar la memoria utilizada por las variables? Se que el GC se encargará de estos recursos administrados en el momento que lo tenga que hacer, pero a mi entender, ya que estamos prestando tanta atención a estas cosas, mejor liberar todo de una.
     
    Te habrás dado cuenta con las preguntas lo principiante que soy con esta tecnología, pero sucede el tema de liberación de recursos no esta muy tratado, y nadie del rubro me sabe aclarar mis dudas.
     
    Te mando un gran saludo, y espero que sigas adelante con esta maravillosa fuente de información.
    Daniel.
    1 月 30 日

    トラックバック

    この記事のトラックバックの URL は次のとおりです。
    http://misopiniones.spaces.live.com/blog/cns!2737DC89A4AAB26B!476.trak
    この記事を参照しているブログ
    • なし