EDIT Zobacz komentarz edytujący na dole pytania, aby uzyskać dodatkowe informacje.Utylizacja MemoryCache w Finalizerze zgłasza wyjątek AccessViolationException
Original pytanie
Mam klasy CacheWrapper która tworzy i utrzymuje na wystąpienie klasy .NET MemoryCache
wewnętrznie.
MemoryCache
dołącza się do zdarzeń AppDomain, więc nigdy nie będzie zbierane śmieci, chyba że zostanie wyraźnie usunięty. Można to sprawdzić za pomocą następującego kodu:
Func<bool, WeakReference> create = disposed => {
var cache = new MemoryCache("my cache");
if (disposed) { cache.Dispose(); }
return new WeakReference(cache);
};
// with false, we loop forever. With true, we exit
var weakCache = create(false);
while (weakCache.IsAlive)
{
"Still waiting...".Dump();
Thread.Sleep(1000);
GC.Collect();
GC.WaitForPendingFinalizers();
}
"Cleaned up!".Dump();
Ze względu na zachowanie, wierzę, że moje wystąpienie MemoryCache powinny być traktowane jako zasób niekontrolowana. Innymi słowy, powinienem upewnić się, że jest on umieszczony w finalizatorze CacheWrapper (sama CacheWrapper jest jednorazowa, zgodnie ze standardowym wzorem Dispose (bool)).
Jednak stwierdzam, że powoduje to problemy, gdy mój kod działa jako część aplikacji ASP.NET. Po wyładowaniu domeny aplikacji finalizator działa na mojej klasie CacheWrapper. To z kolei próbuje zlikwidować instancję MemoryCache
. To tutaj napotykam na problemy. Wydaje się, że Dispose
próbuje załadować informacje konfiguracyjne z IIS, która nie (prawdopodobnie dlatego, że jestem w środku rozładunku domenę aplikacji, ale nie jestem pewien Oto zrzut stosu mam.
MANAGED_STACK:
SP IP Function
000000298835E6D0 0000000000000001 System_Web!System.Web.Hosting.UnsafeIISMethods.MgdGetSiteNameFromId(IntPtr, UInt32, IntPtr ByRef, Int32 ByRef)+0x2
000000298835E7B0 000007F7C56C7F2F System_Web!System.Web.Configuration.ProcessHostConfigUtils.GetSiteNameFromId(UInt32)+0x7f
000000298835E810 000007F7C56DCB68 System_Web!System.Web.Configuration.ProcessHostMapPath.MapPathCaching(System.String, System.Web.VirtualPath)+0x2a8
000000298835E8C0 000007F7C5B9FD52 System_Web!System.Web.Hosting.HostingEnvironment.MapPathActual(System.Web.VirtualPath, Boolean)+0x142
000000298835E940 000007F7C5B9FABB System_Web!System.Web.CachedPathData.GetPhysicalPath(System.Web.VirtualPath)+0x2b
000000298835E9A0 000007F7C5B99E9E System_Web!System.Web.CachedPathData.GetConfigPathData(System.String)+0x2ce
000000298835EB00 000007F7C5B99E19 System_Web!System.Web.CachedPathData.GetConfigPathData(System.String)+0x249
000000298835EC60 000007F7C5BB008D System_Web!System.Web.Configuration.HttpConfigurationSystem.GetApplicationSection(System.String)+0x1d
000000298835EC90 000007F7C5BAFDD6 System_Configuration!System.Configuration.ConfigurationManager.GetSection(System.String)+0x56
000000298835ECC0 000007F7C63A11AE System_Runtime_Caching!Unknown+0x3e
000000298835ED20 000007F7C63A1115 System_Runtime_Caching!Unknown+0x75
000000298835ED60 000007F7C639C3C5 System_Runtime_Caching!Unknown+0xe5
000000298835EDD0 000007F7C7628D86 System_Runtime_Caching!Unknown+0x86
// my code here
Czy istnieje znany rozwiązaniem tego problemu? Czy mam rację sądząc, że nie trzeba wyrzucać MemoryCache
w finalizatora?
EDIT
This article sprawdza odpowiedź Dan Bryanta i omawia wiele interesujących szczegółów. W szczególności W tym przypadku obejmuje on przypadek StreamWriter
, który ma podobny scenariusz do mojego, ponieważ chce go opróżnić podczas usuwania. Oto, co mówi artykuł:
Ogólnie rzecz biorąc, finalizatorzy mogą nie mieć dostępu do zarządzanych obiektów. Jednak obsługa logiki zamykania jest niezbędna w przypadku rozsądnie złożonego oprogramowania . Przestrzeń nazw Windows.Forms obsługuje to z Application.Exit, który inicjuje uporządkowane zamykanie. Kiedy projektujemy komponenty biblioteki, pomocne jest posiadanie sposobu, w jaki wspiera logikę zamykania zintegrowaną z istniejącym logicznie podobnym IDisposable (unikając konieczności definiowania interfejsu bez wbudowanej obsługi języka). Ta jest zwykle wykonywana przez obsługę regularnego wyłączania, gdy wywoływana jest opcja IDisposable.Dispose, a nieudane wyłączenie, gdy nie jest to . Byłoby jeszcze lepiej, gdyby finalizator mógł być użyty do rutynowego wyłączenia, gdy tylko było to możliwe.
Microsoft również wystąpił przeciwko temu problemowi. Klasa StreamWriter jest właścicielem obiektu Stream; StreamWriter.Close opróżni swoje bufory i , a następnie wywoła Stream.Close. Jeśli jednak StreamWriter nie został zamknięty, jego finalizator nie może opróżnić swoich buforów.Microsoft "rozwiązał" ten problem, nie dając StreamWriterowi finalizatora, mając nadzieję, że programiści uzyskają brakujące dane i wyprowadzą ich błąd. Jest to idealny przykład potrzeby logiki wyłączania.
Uważam, że powinno być możliwe wdrożenie "zarządzanej finalizacji" za pomocą WeakReference. Zasadniczo, niech twoja klasa zarejestruje WeakReference dla siebie i sfinalizuje akcję z jakąś kolejką, kiedy obiekt zostanie utworzony. Kolejka jest następnie monitorowana przez wątek tła lub licznik czasu, który wywołuje odpowiednią akcję, gdy jest sparowany, WeakReference zostaje zebrany. Oczywiście, musisz uważać, aby Twoja sfinalizowana akcja nie zatrzymała się na samej klasie, co całkowicie uniemożliwiło jej zbieranie!
Dlaczego spadamy? – ChaseMedallion
Jestem prawie pewny, że nigdy nie należy pozbywać się zarządzanego obiektu w finalizatorze, zamiast tego powinno się to odbywać w normalnym 'IDisposable.Dispose'. Finalizator powinien tylko czyścić niezarządzane obiekty. – juharr
@juharr: W tym przypadku obiekt jest "niezarządzany" w tym sensie, że NIGDY nie będzie zbierany śmieci, chyba że zostanie wyraźnie usunięty. – ChaseMedallion