c# - solo - ¿Se eliminan los objetos IDisponibles si el programa se cierra inesperadamente?
se me cierran los juegos de steam (6)
¿Qué sucede si el programa sale de forma inesperada (ya sea por excepción o el proceso se termina)? ¿Hay situaciones como esta (o de otra manera) donde el programa terminará, pero los objetos IDisposable
no se eliminarán correctamente?
La razón por la que pregunto es porque estoy escribiendo un código que se comunicará con un periférico, y quiero asegurarme de que no haya ninguna posibilidad de que quede en mal estado.
Además de la respuesta de Patrick Hofman y Alexei, la limpieza puede no realizarse incluso si la aplicación finaliza correctamente.
Como probablemente sepa, no se llama al método Dispose
cuando el recolector de basura recopila el objeto que implementa IDisposable
interfaz IDisposable
. Pero el GC llamará al método Finalize
, también conocido como finalizador. En ella debes escribir tu lógica de limpieza usando el patrón de desecho . Y sí, .Net Framework intentará ejecutar todos los finalizadores, pero no hay garantía de que alguna vez se ejecuten.
Como ejemplo, el siguiente programa tiene el finalizador de ejecución muy larga. Por lo tanto, .Net terminará el proceso y nunca verá el mensaje.
class FinalizableObject
{
~FinalizableObject()
{
Thread.Sleep(50000);
Console.WriteLine("Finalized");
}
}
class Program
{
static void Main(string[] args)
{
new FinalizableObject();
}
}
Esto puede deberse a cualquier operación de larga duración, como liberar un identificador de red u otra cosa que requiera mucho tiempo.
Por lo tanto, nunca debe confiar en finalizadores y objetos desechables. Pero todos los controladores abiertos para los objetos del núcleo se cerrarán automáticamente, por lo que no debe preocuparse por ellos.
Le recomendaré que lea algunos artículos interesantes sobre los finalizadores y el GC además de las respuestas:
IDisposable es solo una interfaz. No hay absolutamente nada especial en la forma en que se manejan. Cuando llama a Dispose en un IDisposible (explícitamente o mediante un bloque de uso), llama al contenido de su método de Dispose. Obtiene la basura recogida como cualquier otro objeto.
El propósito de la interfaz es permitir que un implementador defina la limpieza de un tipo que puede tener recursos administrados o no administrados que deben limpiarse explícitamente.
Si todos estos recursos están administrados, la recolección de basura puede ser suficiente y las implementaciones pueden ser solo para optimización.
Si no están administrados o tienen alguna conexión con recursos no administrados, la recolección de basura probablemente no sea suficiente. Esta es la razón por la que la implementación completa recomendada de IDisposable implica el manejo tanto de la eliminación explícita como de la eliminación en tiempo de ejecución (a través de un finalizador).
El proceso no se cerrará y no se garantiza que los finalizadores se ejecuten ... por lo que debe esperar que la destrucción del proceso sea suficiente por sí misma.
Sí, hay tales situaciones. Por ejemplo, llamar a TerminateProcess
, llamar a Environment.FailFast
o encontrar un error interno de CLR hará que el proceso salga sin ejecutar ningún código adicional. En tales situaciones, lo mejor que puedes hacer es decir "bueno".
Incluso si el proceso no sale de forma inesperada, llamar a Dispose
es una acción manual. No es algo que se realiza a través del tiempo de ejecución, excepto cuando un objeto que implementa un finalizador que llama a Dispose
se recolecta como basura. Por lo tanto, olvidarse de envolver un desechable en un using
o causar una pérdida de memoria que mantiene el objeto vivo es otra forma en la que nunca se puede llamar Dispose
.
El sistema operativo realiza la única limpieza confiable cuando sale un proceso: todos los controladores abiertos de los objetos del sistema están cerrados. Cuando se cierra el último identificador, se realiza la limpieza implementada en el sistema operativo o un controlador. Si este código de limpieza no es parte de un controlador pero se supone que debe ser llamado por un proceso de usuario, todo lo que puede hacer es hacer que su código sea lo más sólido posible, o implementar un proceso de vigilancia que maneje la limpieza por usted.
Si el programa se cierra de forma inesperada (por ejemplo, cancela el proceso), no hay absolutamente ninguna garantía de que se IDisposable.Dispose
método IDisposable.Dispose
. Será mejor que no confíes en él para tales eventos. El código de Dispose debe llamarse manualmente por su código, no es algo que el CLR llamará automáticamente por usted.
Si la causa es una excepción y se lanza desde un bloque que using
o un bloque de try catch finally
, se eliminará como se debe. Si no es atrapado por un bloque que using
, no se elimina automáticamente (como no lo hace cuando la aplicación se cierra correctamente).
Una muestra:
IDisposable d1 = new X();
using (IDisposable d2 = new X())
{
throw new NotImplementedException();
}
d1.Dispose();
d1
no está dispuesto, d2
general es. Algunos tipos de excepciones pueden evitar el manejo del using
bloques y también algunos bloqueos de programas. Si la causa es un fallo de alimentación o un fallo del sistema, no hay nada que pueda hacer, por supuesto.
Una prueba muy simple que usa una aplicación de consola ilustra que Dispose no se llama al proceso de eliminación:
class DisposableTest : IDisposable
{
public void Dispose()
{
Console.WriteLine("Dispose called");
}
}
...
using (DisposableTest sw = new DisposableTest())
{
Thread.Sleep(20000);
}
Matar el proceso con el Administrador de tareas no Disposable.Dispose()
método Disposable.Dispose()
. Esperando por 20 segundos lo hará.
Así que, como ya se mencionó, no confíe en los objetos desechables cuando la aplicación se bloquea o muere. Sin embargo, las excepciones deberían activarlo. Me pregunto si una excepción como Exception
o OutOfMemoryException
siempre activará Dispose ().
[editar]
Acabo de probar mis curiosidades:
-
Exception
termina el proceso, por lo que no se llama a Dispose () -
OutOfMemoryException
permite la llamada normal de Dispose ()