编辑 请参见问题底部的编辑说明以获取其他细节。
原始问题
我有一个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本身是可处理的,并遵循标准的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
?
编辑
这篇文章验证了Dan Bryant的答案,并讨论了许多有趣的细节。特别是,它涵盖了StreamWriter
的情况,因为它想要在处理时刷新缓冲区,与我的情况类似。以下是文章的内容:
所有这些说法,我认为应该可以使用WeakReference实现“托管终结”。基本上,当对象创建时,让您的类向自己注册一个WeakReference和一个finalize操作,并将其与某个队列一起使用。然后,由后台线程或计时器监视队列,当它配对的WeakReference被收集时,调用相应的操作。当然,您必须小心,确保您的finalize操作不会意外地保留类本身,从而完全防止收集!
IDisposable.Dispose
中处理。终结器只应该清理非托管对象。 - juharr