Así que estaba leyendo una pregunta sobre cómo obligar al recolector de basura de C # a ejecutar donde casi todas las respuestas son iguales: puede hacerlo, pero no debe, a excepción de algunos casos muy raros . Lamentablemente, nadie allí explica cuáles son esos casos.
¿Me puede decir en qué tipo de escenario es realmente una idea buena o razonable forzar la recolección de basura?
No estoy pidiendo casos específicos de C #, sino todos los lenguajes de programación que tienen un recolector de basura. Sé que no puede forzar GC en todos los idiomas, como Java, pero supongamos que sí.
Respuestas:
Realmente no puede hacer declaraciones generales sobre la forma adecuada de utilizar todas las implementaciones de GC. Varían enormemente. Así que hablaré con el .NET al que te referiste originalmente.
Debe conocer el comportamiento del GC bastante íntimamente para hacerlo con lógica o razón.
El único consejo sobre la colección que puedo dar es: nunca lo hagas.
Si realmente conoce los intrincados detalles del GC, no necesitará mi consejo, por lo que no importará. Si no lo sabe ya con 100% de confianza que le ayudará, y tienen que buscar en línea y encontrar una respuesta como esta: Usted no debe estar llamando GC.Collect , o alternativamente: Usted debe ir a conocer los detalles de cómo funciona el GC por dentro y por fuera, y solo entonces sabrás la respuesta .
Hay un lugar seguro donde tiene sentido usar GC .
GC.Collect es una API disponible que puede usar para perfilar los tiempos de las cosas. Puede perfilar un algoritmo, recopilar y perfilar otro algoritmo inmediatamente después sabiendo que GC del primer algo no estaba ocurriendo durante el segundo sesgando los resultados.
Este tipo de perfil es la única vez que sugeriría recopilar manualmente a cualquiera.
Ejemplo contribuido de todos modos
Un posible caso de uso es que si carga cosas realmente grandes, terminarán en el Montón de objetos grandes que irá directamente a Gen 2, aunque nuevamente Gen 2 es para objetos de larga vida porque se acumula con menos frecuencia. Si sabe que está cargando objetos de corta duración en Gen 2 por cualquier motivo, puede borrarlos más rápidamente para mantener su Gen 2 más pequeño y sus colecciones más rápido.
Este es el mejor ejemplo que se me ocurre, y no es bueno: la presión de LOH que está generando aquí provocaría colecciones más frecuentes, y las colecciones son tan frecuentes como es posible, es probable que elimine el LOH de la misma manera que tan rápido como lo soplabas con objetos temporales. Simplemente no confío en mí mismo para presumir una mejor frecuencia de recolección que el GC en sí, sintonizado por personas mucho más inteligentes que yo.
Así que hablemos sobre algunas de las semánticas y mecanismos en .NET GC ... o ...
Todo lo que creo saber sobre el GC .NET
Por favor, cualquiera que encuentre errores aquí, corrígeme. Se sabe que gran parte de la GC es magia negra y, aunque traté de omitir detalles de los que no estaba seguro, probablemente todavía me equivoqué.
A continuación, faltan intencionalmente numerosos detalles de los que no estoy seguro, así como una gran cantidad de información que simplemente desconozco. Utilice esta información bajo su propio riesgo.
Conceptos de GC
El .NET GC ocurre en momentos inconsistentes, por eso se llama "no determinista", esto significa que no puede confiar en que ocurra en momentos específicos. También es un recolector de basura generacional, lo que significa que divide sus objetos en la cantidad de GC que han pasado.
Los objetos en el montón Gen 0 han vivido a través de 0 colecciones, estas se han hecho recientemente, por lo que no se ha producido ninguna colección desde su creación. Los objetos en su montón Gen 1 han vivido a través de un pase de colección, y del mismo modo los objetos en su montón Gen 2 han vivido a través de 2 pases de colección.
Ahora vale la pena señalar la razón por la que califica estas generaciones y particiones específicas en consecuencia. .NET GC solo reconoce estas tres generaciones, porque los pases de recolección que pasan por estos tres montones son todos ligeramente diferentes. Algunos objetos pueden sobrevivir a la recolección de miles de veces. El GC simplemente los deja al otro lado de la partición de almacenamiento dinámico Gen 2, no tiene sentido dividirlos en otro lugar porque en realidad son Gen 44; el pase de colección sobre ellos es el mismo que todo en el montón de Gen 2.
Hay propósitos semánticos para estas generaciones específicas, así como mecanismos implementados que los honran, y los abordaré en un momento.
¿Qué hay en una colección?
El concepto básico de un pase de colección GC es que verifica cada objeto en un espacio de almacenamiento dinámico para ver si todavía hay referencias vivas (raíces GC) a estos objetos. Si se encuentra una raíz GC para un objeto, significa que el código que se está ejecutando actualmente puede alcanzar y usar ese objeto, por lo que no se puede eliminar. Sin embargo, si no se encuentra una raíz GC para un objeto, significa que el proceso en ejecución ya no necesita el objeto, por lo que puede eliminarlo para liberar memoria para nuevos objetos.
Ahora, una vez que haya terminado de limpiar un montón de objetos y dejar algunos en paz, habrá un efecto secundario desafortunado: espacios libres entre los objetos vivos donde se eliminaron los muertos. Esta fragmentación de la memoria, si se deja sola, simplemente desperdiciaría la memoria, por lo que las colecciones normalmente harán lo que se llama "compactación", donde toman todos los objetos vivos que quedan y los aprietan en el montón para que la memoria libre sea contigua en un lado del montón para Gen 0.
Ahora, dada la idea de 3 montones de memoria, todos particionados por el número de pases de colección que han vivido, hablemos de por qué existen estas particiones.
Colección Gen 0
Siendo Gen 0 los objetos más nuevos, tiende a ser muy pequeño, por lo que puede recolectarlo con seguridad con mucha frecuencia . La frecuencia garantiza que el montón se mantenga pequeño y las colecciones sean muy rápidas porque se están acumulando en un montón tan pequeño. Esto se basa más o menos en una heurística que afirma: una gran mayoría de los objetos temporales que crea son muy temporales, por lo que ya no se usarán ni se referenciarán casi inmediatamente después del uso, por lo que se pueden recolectar.
Colección Gen 1
Gen 1, siendo objetos que no cayeron en esta categoría muy temporal de objetos, aún puede ser de corta duración, porque aún así, una gran parte de los objetos creados no se usan por mucho tiempo. Por lo tanto, Gen 1 también recolecta con bastante frecuencia, nuevamente manteniendo su montón pequeño para que sus colecciones sean rápidas. Sin embargo, la suposición es menos de TI de objetos que son temporales Gen 0, por lo que se acumula con menos frecuencia que Gen 0
Diré francamente que no conozco los mecanismos técnicos que difieren entre el pase de recolección de Gen 0 y el Gen 1, si hay alguno que no sea la frecuencia que recolectan.
Colección Gen 2
Gen 2 ahora debe ser la madre de todos los montones, ¿verdad? Bueno, sí, eso es más o menos correcto. Es donde viven todos sus objetos permanentes: el objeto en el que
Main()
vive, por ejemplo, y todo lo que haceMain()
referencia porque se arraigará hasta queMain()
regrese al final de su proceso.Dado que Gen 2 es un cubo para básicamente todo lo que las otras generaciones no pudieron recolectar, sus objetos son en gran medida permanentes, o al menos de larga duración. Por lo tanto, reconocer muy poco de lo que hay en Gen 2 en realidad será algo que se puede recopilar, no es necesario que se recopile con frecuencia. Esto permite que su colección también sea más lenta, ya que se ejecuta con mucha menos frecuencia. Entonces, esto es básicamente donde han agregado todos los comportamientos adicionales para escenarios extraños, porque tienen tiempo para ejecutarlos.
Montón de objetos grandes
Un ejemplo de los comportamientos adicionales de Gen 2 es que también realiza la recopilación en el Montón de objetos grandes. Hasta ahora he estado hablando completamente sobre el montón de objetos pequeños, pero el tiempo de ejecución de .NET asigna cosas de ciertos tamaños a un montón separado debido a lo que denominé compactación anterior. La compactación requiere mover objetos cuando las colecciones terminan en el montón de objetos pequeños. Si hay un objeto vivo de 10 MB en Gen 1, le llevará mucho más tiempo completar la compactación después de la colección, lo que ralentizará la colección de Gen 1. De modo que ese objeto de 10mb se asigna al Montón de objetos grandes y se recopila durante Gen 2, que se ejecuta con poca frecuencia.
Finalizacion
Otro ejemplo son los objetos con finalizadores. Coloca un finalizador en un objeto que hace referencia a recursos más allá del alcance de .NETs GC (recursos no administrados). El finalizador es la única forma en que el GC puede exigir que se recopile un recurso no administrado: usted implementa su finalizador para realizar la recolección / eliminación / liberación manual del recurso no administrado para garantizar que no se filtre en su proceso. Cuando el GC llega a ejecutar su finalizador de objetos, su implementación borrará el recurso no administrado, haciendo que el GC sea capaz de eliminar su objeto sin correr el riesgo de una fuga de recursos.
El mecanismo con el que los finalizadores hacen esto es mediante referencia directa en una cola de finalización. Cuando el tiempo de ejecución asigna un objeto con un finalizador, agrega un puntero a ese objeto a la cola de finalización y bloquea su objeto en su lugar (llamado fijación) para que la compactación no lo mueva, lo que rompería la referencia de la cola de finalización. A medida que ocurren los pases de recopilación, eventualmente se descubrirá que su objeto ya no tiene una raíz GC, pero la finalización debe ejecutarse antes de que pueda recopilarse. Por lo tanto, cuando el objeto está muerto, la colección moverá su referencia de la cola de finalización y colocará una referencia en lo que se conoce como la cola "FReachable". Luego la colección continúa. En otro momento "no determinista" en el futuro, un hilo separado conocido como el hilo Finalizador pasará por la cola FReachable, ejecutando los finalizadores para cada uno de los objetos referenciados. Una vez finalizado, la cola FReachable está vacía y se ha volteado un poco en el encabezado de cada objeto que dice que no necesitan finalización (este bit también se puede voltear manualmente con
GC.SuppressFinalize
que es común en losDispose()
métodos), también sospecho que ha desanclado los objetos, pero no me cite al respecto. La próxima colección que aparezca en cualquier montón en el que se encuentre este objeto, finalmente la recopilará. Las colecciones Gen 0 ni siquiera prestan atención a los objetos con ese bit de finalización necesario, los promueve automáticamente, sin siquiera verificar su raíz. Un objeto no enraizado que necesita finalización en Gen 1, será arrojado a laFReachable
cola, pero la colección no hace nada más con él, por lo que vive en Gen 2. De esta manera, todos los objetos que tienen un finalizador, y noGC.SuppressFinalize
será recolectado en Gen 2.fuente
Te daré algunos ejemplos. En general, es raro que forzar un GC sea una buena idea, pero puede valer la pena. Esta respuesta es de mi experiencia con literatura .NET y GC. Debe generalizarse bien a otras plataformas (al menos aquellas que tienen un GC significativo).
Si su objetivo es el rendimiento, cuanto más raro sea el GC, mejor. En esos casos, forzar una colección no puede tener un impacto positivo (excepto por cuestiones más bien artificiales, como aumentar la utilización de la memoria caché de la CPU mediante la eliminación de objetos muertos intercalados en los activos). La colección por lotes es más eficiente para todos los coleccionistas que conozco. Para aplicaciones de producción en consumo de memoria en estado estable, inducir un GC no ayuda.
Los ejemplos dados anteriormente apuntan a la consistencia y la limitación del uso de la memoria. En esos casos, los GC inducidos pueden tener sentido.
Parece haber una idea generalizada de que el GC es una entidad divina que induce una colección siempre que sea realmente óptimo hacerlo. Ningún GC que conozco es tan sofisticado y, de hecho, es muy difícil ser óptimo para el GC. El GC sabe menos que el desarrollador. Sus heurísticas se basan en contadores de memoria y cosas como la tasa de recolección, etc. Las heurísticas suelen ser buenas, pero no capturan cambios repentinos en el comportamiento de la aplicación, como la liberación de grandes cantidades de memoria administrada. También es ciego a los recursos no administrados y a los requisitos de latencia.
Tenga en cuenta que los costos de GC varían con el tamaño del montón y el número de referencias en el montón. En un montón pequeño, el costo puede ser muy pequeño. He visto tasas de recopilación de G2 con .NET 4.5 de 1-2 GB / seg en una aplicación de producción con un tamaño de almacenamiento dinámico de 1 GB.
fuente
Como principio general, un recolector de basura se recolectará cuando se encuentre con "presión de memoria", y se considera una buena idea no hacerlo en otras ocasiones porque podría causar problemas de rendimiento o incluso pausas notables en la ejecución de su programa. Y, de hecho, el primer punto depende del segundo: para un recolector de basura generacional, al menos, se ejecuta de manera más eficiente cuanto mayor sea la proporción de basura a objetos buenos, por lo que para minimizar la cantidad de tiempo dedicado a pausar el programa , tiene que postergar y dejar que la basura se acumule tanto como sea posible.
El momento apropiado para invocar manualmente al recolector de basura, entonces, es cuando ha terminado de hacer algo que 1) es probable que haya creado mucha basura, y 2) el usuario espera que tome un tiempo y deje el sistema sin responder de todas formas. Un ejemplo clásico es al final de cargar algo grande (un documento, un modelo, un nuevo nivel, etc.)
fuente
Una cosa que nadie ha mencionado es que, si bien el GC de Windows es increíblemente bueno, el GC en Xbox es basura (juego de palabras) .
Entonces, al codificar un juego XNA destinado a ejecutarse en XBox, es absolutamente crucial programar la recolección de basura en los momentos oportunos, o tendrá un horrible hipo intermitente de FPS. Además, en XBox es común usar
struct
s way, mucho más a menudo de lo que lo haría normalmente, para minimizar la cantidad de objetos que se deben recolectar.fuente
La recolección de basura es ante todo una herramienta de administración de memoria. Como tal, los recolectores de basura se recolectarán cuando haya presión de memoria.
Los recolectores de basura modernos son muy buenos y están mejorando, por lo que es poco probable que pueda mejorarlos recolectando manualmente. Incluso si puede mejorar las cosas hoy, puede ser que una mejora futura en el recolector de basura elegido haga que su optimización sea ineficaz o incluso contraproducente.
Sin embargo , los recolectores de basura generalmente no intentan optimizar el uso de recursos distintos de la memoria. En entornos de recolección de basura, la mayoría de los recursos valiosos que no son de memoria tienen un
close
método o similar, pero hay algunas ocasiones en las que este no es el caso por alguna razón, como la compatibilidad con una API existente.En estos casos, puede tener sentido invocar manualmente la recolección de basura cuando sabe que se está utilizando un recurso valioso que no es de memoria.
RMI
Un ejemplo concreto de esto es la invocación de método remoto de Java. RMI es una biblioteca de llamadas a procedimientos remotos. Por lo general, tiene un servidor, que pone a disposición de los clientes diversos objetos para su uso. Si un servidor sabe que ningún cliente está utilizando un objeto, entonces ese objeto es elegible para la recolección de basura.
Sin embargo, la única forma en que el servidor sabe esto es si el cliente se lo dice, y el cliente solo le dice al servidor que ya no necesita un objeto una vez que el cliente ha recolectado basura lo que sea que lo esté usando.
Esto presenta un problema, ya que el cliente puede tener mucha memoria libre, por lo que no puede ejecutar la recolección de basura con mucha frecuencia. Mientras tanto, el servidor puede tener muchos objetos no utilizados en la memoria, que no puede recopilar porque no sabe que el cliente no los está utilizando.
La solución en RMI es que el cliente ejecute la recolección de basura periódicamente, incluso cuando tiene mucha memoria libre, para garantizar que los objetos se recopilen rápidamente en el servidor.
fuente
using
bloque o llamar a unClose
método para asegúrese de que el recurso se descarte lo antes posible. Confiar en GC para limpiar recursos que no son de memoria no es confiable y causa todo tipo de problemas (particularmente con archivos que necesitan ser bloqueados para acceder, por lo que solo pueden abrirse una vez).close
método disponible (o el recurso se puede usar con unusing
bloque), este es el enfoque correcto. La respuesta trata específicamente los casos raros en los que estos mecanismos no están disponibles.La mejor práctica es no forzar una recolección de basura en la mayoría de los casos. (Todos los sistemas en los que he trabajado han forzado la recolección de basura, han subrayado problemas que, de haber sido resueltos, habrían eliminado la necesidad de forzar la recolección de basura y acelerado el sistema en gran medida).
Hay algunos casos en los que sabe más sobre el uso de la memoria que el recolector de basura. Es poco probable que esto sea cierto en una aplicación multiusuario o en un servicio que responde a más de una solicitud a la vez.
Sin embargo, en algunos tipos de procesamiento por lotes, usted sabe más que el GC. Por ejemplo, considere una aplicación que.
Es posible que pueda hacer un caso (después de una cuidadosa) prueba de que debe forzar una recolección de basura completa después de haber procesado cada archivo.
Otro caso es un servicio que se activa cada pocos minutos para procesar algunos elementos y no mantiene ningún estado mientras está dormido . A continuación, obligando a una colección completa justo antes de irse a dormir puede valer la pena.
Preferiría tener una API de recolección de basura cuando pudiera darle pistas sobre este tipo de cosas sin tener que forzar un GC por mí mismo.
Ver también " Tidbits de rendimiento de Rico Mariani "
fuente
Hay varios casos en los que es posible que desee llamar a gc () usted mismo.
gc()
llamada, muy pocos objetos permanezcan y mucho menos se trasladen al espacio de generación anterior ] Cuando va a crear una gran colección de objetos y utilizar mucha memoria. Simplemente desea limpiar tanto espacio como preparación como sea posible. Esto es solo sentido común. Al llamargc()
manualmente, no habrá verificación de gráfico de referencia redundante en parte de esa gran colección de objetos que está cargando en la memoria. En resumen, si ejecutagc()
antes de cargar mucho en la memoria, elgc()
inducido durante la carga ocurre menos al menos una vez cuando la carga comienza a crear presión de memoria.grandeobjetos y es poco probable que cargue más objetos en la memoria. En resumen, pasas de la fase de creación a la fase de uso. Al llamargc()
dependiendo de la implementación, la memoria utilizada será compactada, lo que mejora enormemente la localidad de caché. Esto dará como resultado una mejora masiva en el rendimiento que no obtendrá de la creación de perfiles .gc()
y la implementación de administración de memoria es compatible, creará una continuidad mucho mejor para su memoria física. Esto nuevamente hace que la nueva gran colección de objetos sea más continua y compacta, lo que a su vez mejora el rendimientofuente
By calling gc() depending on implementation, the memory in used will be compacted which massively improves cache locality. This will result in massive improve in performance that you will not get from profiling.
Si asigna una tonelada de objetos seguidos, es probable que ya estén compactados. En todo caso, la recolección de basura puede barajarlos un poco. De cualquier manera, usar estructuras de datos densas y no saltar al azar en la memoria tendrá un mayor impacto. Si está utilizando una ingenua lista vinculada de un elemento por nodo, ninguna cantidad de trucos manuales de GC lo compensará.Un ejemplo del mundo real:
Tenía una aplicación web que utilizaba un conjunto de datos muy grande que rara vez cambiaba y al que debía accederse muy rápidamente (lo suficientemente rápido para la respuesta por pulsación de tecla a través de AJAX).
Lo bastante obvio aquí es cargar el gráfico relevante en la memoria y acceder a él desde allí en lugar de a la base de datos, actualizando el gráfico cuando cambia la base de datos.
Pero al ser muy grande, una carga ingenua habría ocupado al menos 6 GB de memoria con los datos debido al crecimiento en el futuro. (No tengo cifras exactas, una vez que estaba claro que mi máquina de 2 GB estaba tratando de hacer frente a al menos 6 GB, tenía todas las medidas que necesitaba para saber que no iba a funcionar).
Afortunadamente, había una gran cantidad de objetos inmutables en paletas en este conjunto de datos que eran iguales entre sí; Una vez que me di cuenta de que cierto lote era igual a otro lote, pude alias una referencia a la otra permitiendo que se recopilaran muchos datos y, por lo tanto, encajar todo en menos de medio concierto.
Todo bien, pero para esto todavía se agita a través de más de 6 GB de objetos en el espacio de aproximadamente medio minuto para llegar a este estado. Dejado solo, GC no hizo frente; el aumento en la actividad sobre el patrón habitual de la aplicación (mucho menos pesado en desasignaciones por segundo) fue demasiado agudo.
Entonces, llamar periódicamente
GC.Collect()
durante este proceso de construcción significaba que todo funcionaba sin problemas. Por supuesto, no llamé manualmenteGC.Collect()
el resto del tiempo que se ejecuta la aplicación.Este caso del mundo real es un buen ejemplo de las pautas de cuándo debemos usar
GC.Collect()
:La mayoría de las veces, cuando pensé que podría tener un caso en el
GC.Collect()
que valía la pena llamar, porque los puntos 1 y 2 aplicados, el punto 3 sugirió que empeoró las cosas o al menos no mejoró las cosas (y con poca o ninguna mejora, inclinarse hacia no llamar por sobre llamada ya que el enfoque tiene más probabilidades de resultar mejor durante la vida útil de una aplicación).fuente
Tengo un uso para la eliminación de basura que es algo poco ortodoxo.
Existe esta práctica equivocada que, lamentablemente, es muy frecuente en el mundo de C #, de implementar la eliminación de objetos utilizando el lenguaje feo, torpe, poco elegante y propenso a errores conocido como disposición desechable ID . MSDN lo describe en detalle , y muchas personas lo juran, lo siguen religiosamente, pasan horas y horas discutiendo con precisión cómo se debe hacer, etc.
(Tenga en cuenta que lo que estoy llamando feo aquí no es el patrón de eliminación de objetos en sí; lo que estoy llamando feo es el
IDisposable.Dispose( bool disposing )
idioma particular ).Este idioma fue inventado porque supuestamente es imposible garantizar que el recolector de basura siempre invoque el destructor de sus objetos para limpiar los recursos, por lo que las personas realizan la limpieza de los recursos dentro
IDisposable.Dispose()
, y en caso de que lo olviden, también lo intentan una vez más. dentro del destructor. Ya sabes, por si acaso.Pero entonces es
IDisposable.Dispose()
posible que tenga objetos administrados y no administrados para limpiar, pero los administrados no se pueden limpiar cuandoIDisposable.Dispose()
se invoca desde dentro del destructor, porque el recolector de basura ya los ha cuidado en ese momento, por lo que Es esta la necesidad de unDispose()
método separado que acepte unbool disposing
indicador para saber si los objetos administrados y no administrados deben limpiarse, o solo los no administrados.Disculpe, pero esto es una locura.
Voy por el axioma de Einstein, que dice que las cosas deberían ser lo más simples posible, pero no más simples. Claramente, no podemos omitir la limpieza de recursos, por lo que la solución más simple posible debe incluir al menos eso. La siguiente solución más simple consiste en disponer siempre de todo en el momento preciso en que se supone que se debe eliminar, sin complicar las cosas al confiar en el destructor como alternativa.
Ahora, estrictamente hablando, es imposible garantizar que ningún programador cometerá el error de olvidar invocar
IDisposable.Dispose()
, pero lo que podemos hacer es usar el destructor para detectar este error. Es muy simple, en realidad: todo lo que el destructor tiene que hacer es generar una entrada de registro si detecta que eldisposed
indicador del objeto desechable nunca se configurótrue
. Por lo tanto, el uso del destructor no es una parte integral de nuestra estrategia de eliminación, pero es nuestro mecanismo de garantía de calidad. Y dado que esta es una prueba de solo modo de depuración, podemos colocar todo nuestro destructor dentro de un#if DEBUG
bloque, por lo que nunca incurrirá en ninguna penalización de destrucción en un entorno de producción. (ElIDisposable.Dispose( bool disposing )
idioma prescribe queGC.SuppressFinalize()
debe invocarse con precisión para disminuir la sobrecarga de finalización, pero con mi mecanismo es posible evitar por completo esa sobrecarga en el entorno de producción).Lo que se reduce a un argumento eterno de error duro versus error suave : el
IDisposable.Dispose( bool disposing )
modismo es un enfoque de error suave y representa un intento de permitir que el programador olvide invocarDispose()
sin que el sistema falle, si es posible. El enfoque de error duro dice que el programador siempre debe asegurarse de queDispose()
se invoque. La penalización generalmente prescrita por el enfoque de error duro en la mayoría de los casos es el fracaso de la aserción, pero para este caso particular hacemos una excepción y disminuimos la penalización a una simple emisión de una entrada de registro de error.Por lo tanto, para que este mecanismo funcione, la versión DEBUG de nuestra aplicación debe realizar una eliminación completa de basura antes de salir, para garantizar que se invoquen todos los destructores y, por lo tanto, atrape los
IDisposable
objetos que olvidamos eliminar.fuente
Now, strictly speaking, it is of course impossible to guarantee that no programmer will ever make the mistake of forgetting to invoke IDisposable.Dispose()
En realidad, no lo es, aunque no creo que C # sea capaz de hacerlo. No exponga el recurso; en su lugar, proporcione un DSL para describir todo lo que hará con él (básicamente, una mónada), más una función que adquiere el recurso, hace las cosas, lo libera y devuelve el resultado. El truco consiste en utilizar el sistema de tipos para garantizar que si alguien introduce de contrabando una referencia al recurso, no se puede utilizar en otra llamada a la función de ejecución.Dispose(bool disposing)
(que no está definidoIDisposable
es que se usa para tratar la limpieza de los objetos administrados y no administrados que el objeto tiene como un campo (o de los que es responsable), es resolver el problema incorrecto. objetos no administrados en un objeto administrado sin tener que preocuparse por otros objetos desechables, entonces todos losDispose()
métodos serán uno de esos (haga que el finalizador haga la misma limpieza si es necesario) o solo tenga objetos administrados para desechar (no tenga un finalizador en absoluto), y la necesidad debool disposing
que desaparezca.dispose(disposing)
idioma es terribad, pero lo digo porque las personas a menudo usan esa técnica y finalizadores cuando solo tienen recursos administrados (elDbConnection
objeto, por ejemplo, es administrado , no está pinchado ni ordenado), Y USTED DEBE SOLAMENTE ALGUNA VEZ IMPLEMENTE UN FINALIZADOR CON CÓDIGO NO MANEJADO, PINVOKADO, COM MARSHALLED O INSEGURO . En mi respuesta detallé anteriormente cuán lamentablemente costosos son los finalizadores, no los use a menos que tenga recursos no administrados en su clase.dispose(dispoing)
idioma, pero la verdad es que es tan frecuente porque la gente tiene tanto miedo de las cosas de GC que algo tan poco relacionado como eso (dispose
debería tener mucho que ver con GC) les merece tomar el medicamento recetado sin siquiera investigarlo. Bien por inspeccionarlo, pero te perdiste el conjunto más grande (alienta a los finalizadores más a menudo de lo que deberían ser)Hablando muy teóricamente y sin tener en cuenta problemas como algunas implementaciones de GC que ralentizan las cosas durante sus ciclos de recolección, el escenario más grande que se me ocurre para forzar la recolección de basura es un software de misión crítica donde las fugas lógicas son preferibles a colgar los punteros, por ejemplo, porque se bloquea en momentos inesperados puede costar vidas humanas o algo por el estilo.
Si nos fijamos en algunos de los juegos indies de mala calidad escritos con lenguajes GC como los juegos Flash, se filtran como locos pero no se bloquean. Pueden tomar diez veces la memoria 20 minutos para jugar porque una parte de la base de código del juego olvidó establecer una referencia a nula o eliminarla de una lista, y las velocidades de fotogramas pueden comenzar a sufrir, pero el juego aún funciona. Un juego similar escrito con codificación de mala calidad C o C ++ podría fallar como resultado de acceder a punteros colgantes como resultado del mismo tipo de error de gestión de recursos, pero no se filtraría tanto.
Para los juegos, el bloqueo puede ser preferible en el sentido de que puede detectarse y repararse rápidamente, pero para un programa de misión crítica, estrellarse en momentos totalmente inesperados podría matar a alguien. Por lo tanto, los casos principales que creo serían escenarios en los que no fallar o que otras formas son de seguridad son absolutamente críticos, y una filtración lógica es algo relativamente trivial en comparación.
El escenario principal donde creo que es malo forzar GC es para cosas donde la fuga lógica es en realidad menos preferible que un bloqueo. Con los juegos, por ejemplo, el bloqueo no necesariamente matará a nadie y podría detectarse y repararse fácilmente durante las pruebas internas, mientras que una fuga lógica podría pasar desapercibida incluso después de que se envíe el producto, a menos que sea tan grave que haga que el juego no se pueda jugar en minutos . En algunos dominios, un bloqueo fácilmente reproducible que ocurre en las pruebas a veces es preferible a una fuga que nadie nota de inmediato.
Otro caso en el que puedo pensar que sería preferible forzar GC en un equipo es para un programa de muy corta duración, como algo ejecutado desde la línea de comando que realiza una tarea y luego se apaga. En ese caso, la vida útil del programa es demasiado corta para que cualquier tipo de fuga lógica no sea trivial. Las fugas lógicas, incluso para grandes recursos, generalmente solo se vuelven problemáticas horas o minutos después de ejecutar el software, por lo que es improbable que un software que solo se ejecute durante 3 segundos tenga problemas con fugas lógicas, y podría hacer mucho Es más sencillo escribir programas de corta duración si el equipo acaba de usar GC.
fuente