Cómo ser notificado antes de que se finalicen las variables estáticas
¿Cuándo puedo limpiar objetos almacenados en variables estáticas en C #?
Tengo una variable estática que esinicializado perezosamente:
public class Sqm
{
private static Lazy<Sqm> _default = new Lazy<Sqm>();
public static Sqm Default { get { return _default.Value; } }
}
Nota: Que acabo de cambiarFoo
ser unstatic
clase. No cambia la pregunta de ninguna manera siFoo
Es estático o no. Pero algunas personas están convencidas de que no hay forma de que una instancia deSqm
podría construirse sin construir primero una instancia deFoo
. Incluso si yohizo crear unFoo
objeto; incluso si creé 100 de ellos, no me ayudaría a resolver el problema (de cuándo"limpiar" un miembro estático).
Uso de la muestra
Foo.Default.TimerStart("SaveQuestion");
//...snip...
Foo.Default.TimerStop("SaveQuestion");
Ahora miSqm
La clase implementa un método que debe llamarse cuando el objeto ya no es necesario, y necesita limpiarse (guardar el estado en el sistema de archivo, liberar bloqueos, etc.). Este métododebe ser llamado antes de que se ejecute el recolector de basura (es decir, antes de que se llame al finalizador de mi objeto):
public class Sqm
{
var values = new List<String>();
Boolean shutdown = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Shutdown()
{
if (!alreadyShutdown)
{
Cleanup(values);
alreadyShutdown = true;
}
}
}
¿Cuándo y dónde puedo llamar a miShutdown()
¿método?
Nota: No quiero que el desarrollador queusos laSqm
clase para tener que preocuparse por llamarShutdown
. Ese no es su trabajo. En otros entornos lingüísticos no tendría que hacerlo.
losLazy<T>
la clase no parece llamarDispose
sobre elValue
lo posee perezosamente Así que no puedo enganchar elIDisposable
patrón - y usar eso como el tiempo para llamarShutdown
. Necesito llamarShutdown
mí mismo.
¿Pero cuando?
Es unstatic
variable, existe una vez durante la vida de la aplicación / dominio / dominio de aplicación / apartamento.
Algunas personas entienden, y otras no, que intentar cargar mis datos durante unafinalizer
esincorrecto.
///WRONG: Don't do this!
~Sqm
{
Shutdown(_values); //<-- BAD! _values might already have been finalized by the GC!
}
¿Por qué está mal? Porquevalues
Puede que ya no esté allí. No controlas qué objetos se finalizan en qué orden. Es totalmente posible quevalues
se finalizó antes de la contenciónSqm
.
losIDisposable
interfaz, y laDispose()
método es unconvención. No hay nada que dicte que si mi objeto implementa unDispose()
Método que jamás se llamará. En efecto, yopodría adelante e implementalo:
public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Dispose()
{
if (!alreadyDiposed)
{
Cleanup(values);
alreadyDiposed = true;
}
}
}
Para la persona que realmente lee la pregunta, puede notar que en realidad no cambié nada. Lo único que hice fue cambiar el nombre de un método deApagar aDisponer. El patrón de Disposición es simplemente una convención. Todavía tengo el problema: ¿cuándo puedo llamar?Dispose
?
VocaciónDispose
de mi finalizador es tan incorrecto como llamarShutdown
de mi finalizador (están igualmente equivocados):
public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Dispose()
{
if (!alreadyDiposed)
{
Cleanup(_values); // <--BUG: _values might already have been finalized by the GC!
alreadyDiposed = true;
}
}
~Sqm
{
Dispose();
}
}
Porque, de nuevo,values
Puede que ya no esté allí. Para completar, podemos volver al código completo original correcto:
public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
protected void Dispose(Boolean itIsSafeToAlsoAccessManagedResources)
{
if (!alreadyDiposed)
{
if (itIsSafeToAlsoAccessManagedResources)
Cleanup(values);
alreadyDiposed = true;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
~Sqm
{
Dispose(false); //false ==> it is not safe to access values
}
}
He completado el círculo. tengo un objeto que necesito"limpiar" antes de que el dominio de la aplicación se apague. Algo dentro de mi objeto debe ser notificado cuando puede llamarCleanup
.
No.
Estoy migrando conceptos existentes de otro lenguaje a C #. Si un desarrollador utiliza la instancia global de singleton:
Foo.Sqm.TimerStart();
entonces elSqm
la clase es perezosa inicializada En una aplicación (nativa), se mantiene la referencia al objeto. Durante el cierre de la aplicación (nativa), la variable que mantiene el puntero de la interfaz se establece ennull
, y el objeto singletondestructor
Se llama, y puede limpiarse.
Nadie debería tener que llamar a nada. NoCleanup
noShutdown
noDispose
. El cierre debe ocurrir automáticamente por la infraestructura.
¿Cuál es el equivalente de C # deMe veo yendo, me limpio.?
Se complica por el hecho de que si deja que el recolector de basura recoja el objeto: es demasiado tarde. Es probable que los objetos de estado interno que quiero persistir ya estén finalizados.
Sería fácil si desde ASP.netSi pudiera garantizar que mi clase estaba siendo utilizada desde ASP.net, podría preguntar alHostingEnvironment
para notificar antes de que el dominio se apague registrando mi objeto con él:
System.Web.Hosting.HostingEnvironment.RegisterObject(this);
Y poner en práctica elStop
método:
public class Sqm : IDisposable, IRegisteredObject
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
protected void Dispose(Boolean itIsSafeToAlsoAccessManagedResources)
{
if (!alreadyDiposed)
{
if (itIsSafeToAlsoAccessManagedResources)
Cleanup(values);
alreadyDiposed = true;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
Sqm
{
//Register ourself with the ASP.net hosting environment,
//so we can be notified with the application is shutting down
HostingEnvironment.RegisterObject(this); //asp.net will call Stop() when it's time to cleanup
}
~Sqm
{
Dispose(false); //false ==> it is not safe to access values
}
// IRegisteredObject
protected void Stop(Boolean immediate)
{
if (immediate)
{
//i took too long to shut down; the rug is being pulled out from under me.
//i had my chance. Oh well.
return;
}
Cleanup(); //or Dispose(), both good
}
}
Excepto que mi clase no sabe si me llamaránASP.net, o deWinForms, o deWPF, o una aplicación de consola, o extensión de shell.
Editar: La gente parece estar confundida porqueIDisposable
existe un patrón para. Se eliminaron las referencias aDispose
con el fin de eliminar la confusión.
Editar 2: La gente parece estar exigiendo un código de ejemplo completo y detallado antes de responder la pregunta. Personalmente, creo que la pregunta ya contiene demasiado código de ejemplo, ya que no sirve para ayudar a hacer la pregunta.
Y ahora que he añadido.muuuy Mucho código, la pregunta se ha perdido. La gente se niega a responder una pregunta hasta que la pregunta haya sido justificada. Ahora que ha sido justificado, nadie lo leerá.
Es como diagnosticoEs como elSystem.Diagnostics.Trace
clase. La gente lo llama cuando quiere:
Trace.WriteLine("Column sort: {0} ms", sortTimeInMs);
Y nunca tendré que volver a pensarlo.
Y entonces la desesperación se establece enEstaba lo suficientemente desesperado, que consideré ocultar mi objeto detrás de un COMIUnknown
interfaz, que es referencia contada
public class Sqm : IUnknown
{
IUnknown _default = new Lazy<Sqm>();
}
Y entonces ojalá pudieratruco el CLR en decrementar el recuento de referencia en mi interfaz. Cuando mi recuento de referencias se vuelve cero, sé que todo se está cerrando.
La desventaja de eso es que no puedo hacer que funcione.