Jak otrzymywać powiadomienia, zanim zmienne statyczne zostaną sfinalizowane
Kiedy można czyścić obiekty przechowywane w zmiennych statycznych w C #?
Mam zmienną statycznąleniwie zainicjowany:
public class Sqm
{
private static Lazy<Sqm> _default = new Lazy<Sqm>();
public static Sqm Default { get { return _default.Value; } }
}
Uwaga: Właśnie się zmieniłemFoo
byćstatic
klasa. Nie zmienia to w żaden sposób, jeśliFoo
jest statyczny lub nie. Ale niektórzy ludzie są przekonani, że nie ma możliwości, aby wystąpienieSqm
może być skonstruowany bez uprzedniego skonstruowania instancjiFoo
. Nawet gdybymzrobił StwórzFoo
obiekt; nawet jeśli stworzyłbym ich 100, to nie pomogłoby mi rozwiązać problemu (kiedy do"sprzątać" członek statyczny).
Wykorzystanie próbki
Foo.Default.TimerStart("SaveQuestion");
//...snip...
Foo.Default.TimerStop("SaveQuestion");
Teraz mojeSqm
klasa implementuje metodę, która musi zostać wywołana, gdy obiekt nie jest już potrzebny, i musi się oczyścić (zapisać stan do systemu archiwizacji, zwolnić blokady itp.). Ta metodamusi być wywołane przed uruchomieniem zbieraczy śmieci (tj. przed wywołaniem finalizatora mojego obiektu):
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;
}
}
}
Kiedy i gdzie mogę zadzwonić do mnieShutdown()
metoda?
Uwaga: Nie chcę programisty, któryużywa Sqm
klasa, żeby się martwić o telefonShutdown
. To nie jest jego praca. W innych środowiskach językowych nie musiałby.
TheLazy<T>
klasa nie wydaje się dzwonićDispose
naValue
jest leniwie właścicielem. Więc nie mogę się doczepićIDisposable
wzorzec - i używaj go jako czasu na połączenieShutdown
. Muszę zadzwonićShutdown
siebie.
Ale kiedy?
To jeststatic
zmienna, istnieje raz dla życia aplikacji / domeny / domeny aplikacji / mieszkania.
Niektórzy rozumieją, a niektórzy nie, próbując przesłać moje dane podczasfinalizer
jestźle.
///WRONG: Don't do this!
~Sqm
{
Shutdown(_values); //<-- BAD! _values might already have been finalized by the GC!
}
Dlaczego jest źle? Bovalues
może już tam nie być. Nie kontrolujesz, które obiekty są finalizowane w jakiej kolejności. Jest to całkowicie możliwevalues
został sfinalizowany przed zawartościąSqm
.
TheIDisposable
interfejs iDispose()
metoda toKonwencja. Nic nie dyktuje, że jeśli mój obiekt implementuje aDispose()
metoda, którą kiedykolwiek zostanie nazwana. W rzeczywistościmógłby idź dalej i wdrażaj go:
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;
}
}
}
Osoba, która faktycznie czyta to pytanie, może zauważyć, że właściwie niczego nie zmieniłem. Jedyne, co zrobiłem, to zmienić nazwę metody zZamknąć doDysponować. Wzór Dispose jest po prostu konwencją. Nadal mam problem: kiedy mogę zadzwonićDispose
?
PowołanieDispose
z mojego finalizatora jest tak samo niepoprawne jak dzwonienieShutdown
z mojego finalizatora (są identycznie błędne):
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();
}
}
Bo znowuvalues
może już tam nie być. Aby uzyskać kompletność, możemy powrócić do pełnego oryginalnego poprawnego kodu:
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
}
}
zatoczyłem koło. mam obiekt, który muszę"sprzątać" przed zamknięciem domeny aplikacji. Coś w moim obiekcie musi zostać powiadomione, gdy może zadzwonićCleanup
.
Nie.
Przeprowadzam migrację istniejących pojęć z innego języka do C #. Jeśli deweloper korzysta z globalnej instancji singleton:
Foo.Sqm.TimerStart();
a późniejSqm
klasa jest leniwa zainicjowana. W aplikacji (natywnej) utrzymywane jest odwołanie do obiektu. Podczas zamykania aplikacji (natywnej) zmienna przechowująca wskaźnik interfejsu jest ustawiona nanull
i obiekt singletondestructor
jest wywoływany i może się oczyścić.
Nikt nie powinien nigdy nic dzwonić. NieCleanup
, nieShutdown
, nieDispose
. Wyłączenie powinno nastąpić automatycznie z infrastruktury.
Co to jest odpowiednik C #Widzę, jak odchodzę, sprzątam się?
To skomplikowane przez fakt, że jeśli pozwolisz zbieraczowi śmieci zebrać obiekt: jest za późno. Obiekty stanu wewnętrznego, które chcę utrzymać, są już prawdopodobnie sfinalizowane.
Byłoby łatwo, gdyby z ASP.netGdybym mógł zagwarantować, że moja klasa była używana z ASP.net, mógłbym zapytaćHostingEnvironment
powiadomić, zanim domena zostanie zamknięta, rejestrując przy tym swój obiekt:
System.Web.Hosting.HostingEnvironment.RegisterObject(this);
I zaimplementujStop
metoda:
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
}
}
Z wyjątkiem tego, że moja klasa nie wie, czy będę do mnie dzwonićASP.netlub zWinFormslub zWPFlub aplikacja konsoli lub rozszerzenie powłoki.
Edytować: Ludzie wydają się być zdezorientowanicoIDisposable
istnieje wzór. Usunięto odniesienia doDispose
w celu usunięcia zamieszania.
Edytuj 2: Ludzie wydają się wymagać pełnego, szczegółowego, przykładowego kodu, zanim odpowiedzą na pytanie. Osobiście uważam, że pytanie zawiera już zbyt wiele przykładowego kodu, ponieważ nie służy do zadawania pytań.
A teraz dodałemsooo dużo kodu, pytanie zostało utracone. Ludzie odmawiają odpowiedzi na pytanie, dopóki pytanie nie zostanie uzasadnione. Teraz, kiedy jest to uzasadnione, nikt tego nie przeczyta.
To jak diagnostykaTo jest jakSystem.Diagnostics.Trace
klasa. Ludzie nazywają to, kiedy chcą:
Trace.WriteLine("Column sort: {0} ms", sortTimeInMs);
i nigdy więcej nie muszę o tym myśleć.
A potem zaczyna się desperacjaByłem nawet wystarczająco zdesperowany, że rozważałem ukrycie mojego obiektu za COMIUnknown
interfejs, który jest liczony jako odniesienie
public class Sqm : IUnknown
{
IUnknown _default = new Lazy<Sqm>();
}
I mam nadzieję, że taksztuczka CLR w celu zmniejszenia liczby odwołań w moim interfejsie. Kiedy mój licznik odniesie zero, wiem, że wszystko się wyłącza.
Wadą tego jest to, że nie mogę tego sprawić.