ИЗМЕНИТЬ Дополнительную информацию см. в примечании к редактированию внизу вопроса.
Исходный вопрос
У меня есть класс CacheWrapper, который внутри создает и хранит экземпляр класса .NET MemoryCache
.
MemoryCache
подключается к событиям AppDomain, поэтому он никогда не будет удален сборщиком мусора, если он не будет удален явно. Вы можете проверить это с помощью следующего кода:
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();
Из-за такого поведения я считаю, что мой экземпляр MemoryCache следует рассматривать как неуправляемый ресурс. Другими словами, я должен убедиться, что он расположен в финализаторе CacheWrapper (CacheWrapper сам по себе является Disposable и следует стандартному шаблону Dispose(bool)).
Однако я обнаружил, что это вызывает проблемы, когда мой код запускается как часть приложения ASP.NET. Когда домен приложения выгружается, финализатор запускается в моем классе CacheWrapper. Это, в свою очередь, пытается удалить экземпляр MemoryCache
. Здесь я сталкиваюсь с проблемами. Кажется, что Dispose
пытается загрузить некоторую информацию о конфигурации из IIS, что не удается (предположительно, потому что я нахожусь в процессе выгрузки домена приложения, но я не уверен. Вот дамп стека, который у меня есть:
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
Есть ли известное решение этого? Правильно ли я думаю, что мне нужно удалить MemoryCache
в финализаторе?
ИЗМЕНИТЬ
Эта статья подтверждает отвечает и обсуждает многие интересные детали. В частности, он описывает случай с StreamWriter
, который сталкивается со сценарием, подобным моему, потому что он хочет очистить свои буферы при утилизации. Вот что написано в статье:
Вообще говоря, финализаторы не могут получить доступ к управляемым объектам. Однако поддержка логики выключения необходима для достаточно сложного программного обеспечения. Пространство имен Windows.Forms обрабатывает это с помощью Application.Exit, который инициирует упорядоченное завершение работы. При разработке библиотечных компонентов полезно иметь способ поддержки логики завершения работы, интегрированный с существующим логически подобным IDisposable (это позволяет избежать необходимости определять интерфейс IShutdownable без какой-либо встроенной языковой поддержки). Обычно это делается путем поддержки упорядоченного завершения работы, когда вызывается IDisposable.Dispose, и аварийного завершения работы, когда это не так. Было бы еще лучше, если бы финализатор можно было использовать для упорядоченного завершения работы, когда это возможно.
Microsoft тоже столкнулась с этой проблемой. Класс StreamWriter владеет объектом Stream; StreamWriter.Close очистит свои буферы, а затем вызовет Stream.Close. Однако, если StreamWriter не был закрыт, его финализатор не может очистить свои буферы. Microsoft «решила» эту проблему, не предоставляя StreamWriter финализатор, надеясь, что программисты заметят недостающие данные и выведут свою ошибку. Это прекрасный пример необходимости логики выключения.
Все сказанное, я думаю, должно быть возможно реализовать "управляемую финализацию" с помощью WeakReference. По сути, пусть ваш класс зарегистрирует WeakReference для себя и действие finalize с некоторой очередью при создании объекта. Затем очередь отслеживается фоновым потоком или таймером, который вызывает соответствующее действие при сборе парного WeakReference. Конечно, вы должны быть осторожны, чтобы ваше действие finalize случайно не удерживало сам класс, тем самым полностью предотвращая сбор!
IDisposable.Dispose
. Финализатор должен очищать только неуправляемые объекты. - person juharr   schedule 08.10.2014