EDIT有关更多详细信息,请参阅问题底部的编辑注释。
原问题
我有一个 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
在终结器中?
EDIT
本文 http://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About验证了丹·布莱恩特的回答并讨论了许多有趣的细节。他特别介绍了以下案例:StreamWriter
,它面临着与我类似的情况,因为它希望在处置时刷新其缓冲区。文章是这样说的:
一般来说,终结器不能访问托管对象。
然而,对关闭逻辑的支持是必要的
相当复杂的软件。 Windows.Forms 命名空间处理此问题
使用 Application.Exit,它会启动有序关闭。什么时候
设计库组件,有一种方法是有帮助的
支持与现有集成的关闭逻辑
逻辑上类似的 IDisposable (这避免了必须定义一个
IShutdownable 界面,无需任何内置语言支持)。这
通常是通过支持有序关闭来完成的
调用 IDisposable.Dispose,并在调用时中止关闭
不是。如果终结器可以用来做一个,那就更好了
尽可能有序关闭。
微软也遇到了这个问题。 StreamWriter 类
拥有一个 Stream 对象; StreamWriter.Close 将刷新其缓冲区并
然后调用Stream.Close。然而,如果 StreamWriter 没有关闭,它的
终结器无法刷新其缓冲区。微软“解决”了这个问题
不给 StreamWriter 终结器,希望程序员能够
注意缺失的数据并推断出它们的错误。这是一个完美的
需要关闭逻辑的示例。
综上所述,我认为应该可以使用 WeakReference 实现“托管终结”。基本上,让您的类在创建对象时向其自身注册一个 WeakReference 并使用某个队列进行终结操作。然后,队列由后台线程或计时器监视,当配对的 WeakReference 被收集时,后台线程或计时器会调用适当的操作。当然,您必须小心,您的终结操作不会无意中保留类本身,从而完全阻止收集!