c# - pattern - GC.Collect() y Finalize
disposable c# pattern (5)
De acuerdo, se sabe que GC llama implícitamente a los métodos de Finalize
en los objetos cuando identifica ese objeto como basura. Pero, ¿qué sucede si hago un GC.Collect()
? ¿Los finalizadores todavía se ejecutan? Una pregunta estúpida tal vez, pero alguien me preguntó esto y respondí "Sí" y luego pensé: " ¿Fue eso completamente correcto? "
De acuerdo, se sabe que GC llama implícitamente a los métodos de Finalización en los objetos cuando identifica ese objeto como basura.
No no no. Eso no se sabe porque para ser conocimiento, una declaración debe ser verdadera . Esa declaración es falsa . El recolector de elementos no utilizados ejecuta finalizadores a medida que se rastrean , ya sea que se ejecute solo o si llama a Collect
. La secuencia del finalizador ejecuta los finalizadores después de que el recopilador de búsqueda haya encontrado la basura y eso ocurre de forma asincrónica con respecto a una llamada a Collect
. (Si sucede, tal vez no, como indica otra respuesta). Es decir, no puede confiar en la ejecución del hilo del finalizador antes de que el control regrese de Collect
.
Aquí hay un bosquejo simplificado de cómo funciona:
- Cuando ocurre una recolección, el hilo de rastreo del recolector de basura rastrea las raíces, los objetos que se sabe están vivos, y cada objeto al que se refieren, y así sucesivamente, para determinar los objetos muertos.
- Los objetos "muertos" que tienen finalizadores pendientes se mueven a la cola del finalizador. La cola del finalizador es una raíz . Por lo tanto, esos objetos "muertos" en realidad todavía están vivos .
- El hilo del finalizador, que generalmente es un hilo diferente al hilo de trazado del GC, finalmente se ejecuta y vacía la cola del finalizador. Esos objetos se vuelven realmente muertos y se recopilan en la próxima colección en el hilo de rastreo. (Por supuesto, dado que simplemente sobrevivieron a la primera colección, podrían estar en una generación superior).
Como dije, eso es simplificado; los detalles exactos de cómo funciona la cola del finalizador son un poco más complicados que eso. Pero se lleva suficiente de la idea. El resultado práctico aquí es que no puede asumir que llamar a Collect
también ejecuta finalizadores , porque no es así. Permítanme repetirlo una vez más: la parte de rastreo del recolector de basura no ejecuta los finalizadores , y Collect
solo ejecuta la parte de rastreo del mecanismo de recolección.
Llame a WaitForPendingFinalizers
llamado después de llamar a Collect
si desea garantizar que se hayan ejecutado todos los finalizadores. Eso pausará el hilo actual hasta que el hilo del finalizador llegue a vaciar la cola. Y si quiere asegurarse de que esos objetos finalizados recuperen su memoria, tendrá que llamar a Collect
por segunda vez.
Y, por supuesto, no hace falta decir que solo debe hacer esto para depurar y probar. Nunca hagas esta tontería en el código de producción sin una razón realmente buena.
Cuando se recoge la basura (ya sea en respuesta a la presión de la memoria o GC.Collect()
), los objetos que requieren finalización se ponen en la cola de finalización.
A menos que llame a GC.WaitForPendingFinalizers()
, los finalizadores pueden continuar ejecutándose en segundo plano mucho después de que la recolección de basura haya finalizado.
Por cierto, no hay garantía de que se llamarán finalizadores. Desde MSDN ...
El método Finalize puede no ejecutarse hasta completarse o no ejecutarse en absoluto en las siguientes circunstancias excepcionales:
- Otro finalizador bloquea indefinidamente (entra en un ciclo infinito, intenta obtener un bloqueo que nunca puede obtener y así sucesivamente). Como el tiempo de ejecución intenta ejecutar los finalizadores hasta su finalización, es posible que no se llamen a otros finalizadores si un finalizador bloquea indefinidamente.
- El proceso finaliza sin dar la oportunidad de limpieza al motor de ejecución. En este caso, la primera notificación de terminación del proceso en el tiempo de ejecución es una notificación DLL_PROCESS_DETACH.
El tiempo de ejecución continúa Finalizando objetos durante el apagado solo mientras el número de objetos finalizables continúa disminuyendo.
En realidad, la respuesta "depende". En realidad, hay un hilo dedicado que ejecuta todos los finalizadores. Eso significa que la llamada a GC.Collect
solo desencadenó este proceso y la ejecución de todos los finalizadores se llamaría de forma asíncrona.
Si desea esperar hasta que se llamen todos los finalizadores, puede usar el siguiente truco:
GC.Collect();
// Waiting till finilizer thread will call all finalizers
GC.WaitForPendingFinalizers();
Sí, pero no de inmediato. Este extracto es de Garbage Collection: Automatic Memory Management en Microsoft .NET Framework (MSDN Magazine) (*)
"Cuando una aplicación crea un nuevo objeto, el nuevo operador asigna la memoria del montón. Si el tipo del objeto contiene un método Finalize, se coloca un puntero al objeto en la cola de finalización. La cola de finalización es una estructura de datos interna controlada por el recolector de basura. Cada entrada en la cola apunta a un objeto que debería tener su método Finalizar llamado antes de que la memoria del objeto pueda recuperarse.
Cuando se produce un GC ... el recolector de basura escanea la cola de finalización en busca de punteros a estos objetos. Cuando se encuentra un puntero, el puntero se elimina de la cola de finalización y se agrega a la cola predecible (pronunciado "F-alcanzable"). La cola inalcanzable es otra estructura de datos interna controlada por el recolector de basura. Cada puntero en la cola predecible identifica un objeto que está listo para llamar a su método Finalize.
Hay un hilo especial en tiempo de ejecución dedicado a llamar a los métodos Finalize. Cuando la cola localizable está vacía (que generalmente es el caso), este hilo duerme. Pero cuando aparecen las entradas, este subproceso se activa, elimina cada entrada de la cola y llama al método Finalize de cada objeto. Debido a esto, no debe ejecutar ningún código en un método Finalize que haga una suposición sobre el hilo que está ejecutando el código. Por ejemplo, evite acceder al almacenamiento local de subprocesos en el método Finalize ".
(*) Desde noviembre de 2000, las cosas podrían haber cambiado desde entonces.
Merece la pena mencionar un par de puntos más aquí.
Finalizer es el último punto donde los objetos .net pueden liberar recursos no administrados. Los finalizadores deben ejecutarse solo si no dispone de sus instancias correctamente. Idealmente, los finalizadores nunca deberían ejecutarse en muchos casos. Debido a que la implementación correcta de eliminación debe suprimir la finalización .
Aquí hay un ejemplo para la implementación correcta de IDispoable .
Si llama al método Dispose de cualquier objeto desechable, debe borrar todas las referencias y suprimir la finalización. Si hay un desarrollador no tan bueno que olvida llamar al método Dispose, Finalizer es el salvavidas.