问题描述
编辑有关其他详细信息,请参阅问题底部的编辑说明.
EDITSee edit note at the bottom of the question for additional detail.
原始问题
我有一个CacheWrapper类,该类在内部创建并保留.NET MemoryCache
类的实例.
I have a CacheWrapper class which creates and holds onto an instance of the .NET MemoryCache
class internally.
MemoryCache
将自身挂接到AppDomain事件中,因此除非将其显式处置,否则将永远不会对其进行垃圾收集.您可以使用以下代码进行验证:
MemoryCache
hooks itself into AppDomain events, so it will never be garbage-collected unless it is explicitly disposed. You can verify this with the following code:
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)模式).
Because of the behavior, I believe that my MemoryCache instance should be treated as an unmanaged resource. In other words, I should ensure that it is disposed in the finalizer of CacheWrapper (CacheWrapper is itself Disposable follows the standard Dispose(bool) pattern).
但是,我发现当我的代码作为ASP.NET应用程序的一部分运行时,这会导致问题.卸载应用程序域后,终结器将在我的CacheWrapper类上运行.依次尝试处置MemoryCache
实例.这就是我遇到的问题.似乎Dispose
尝试从IIS加载一些配置信息,但失败了(大概是因为我正在卸载应用程序域,但是我不确定.这是我拥有的堆栈转储:
However, I am finding that this causes issues when my code runs as part of an ASP.NET app. When the application domain is unloaded, the finalizer runs on my CacheWrapper class. This in turn attempts to dispose the MemoryCache
instance. This is where I run into issues. It seems that Dispose
attempts to load some configuration information from IIS, which fails (presumably because I'm in the midst of unloading the app domain, but I'm not sure. Here's the stack dump I have:
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
是正确的吗?
Is there any known solution to this? Am I correct in thinking that I do need to dispose the MemoryCache
in the finalizer?
编辑
本文验证了丹·布莱恩特的答案,并讨论了许多有趣的细节.特别是,他介绍了StreamWriter
的情况,该情况与我的情况类似,因为它希望在处理后刷新其缓冲区.这是文章说的:
This article validates Dan Bryant's answer and discusses many of the interesting details. In particular, he covers the case of StreamWriter
, which faces a similar scenario to mine because it wants to flush it's buffers upon disposal. Here's what the article says:
Microsoft也遇到了这个问题. StreamWriter类 拥有一个Stream对象; StreamWriter.Close将刷新其缓冲区并 然后调用Stream.Close.但是,如果未关闭StreamWriter,则其 终结器无法刷新其缓冲区. Microsoft通过以下方式解决"了此问题: 没有给StreamWriter终结器,希望程序员能够 注意丢失的数据并推断出它们的错误.这是完美的 需要关闭逻辑的示例.
Microsoft came up against this problem, too. The StreamWriter class owns a Stream object; StreamWriter.Close will flush its buffers and then call Stream.Close. However, if a StreamWriter was not closed, its finalizer cannot flush its buffers. Microsoft "solved" this problem by not giving StreamWriter a finalizer, hoping that programmers will notice the missing data and deduce their error. This is a perfect example of the need for shutdown logic.
所有这些,我认为应该可以使用WeakReference实现托管定稿".基本上,在创建对象时,让您的类为其自身注册一个WeakReference并使用一些队列来完成操作.然后,该队列由后台线程或计时器监视,该线程或计时器在配对WeakReference配对时调用适当的操作.当然,您必须要小心,您的finalize操作不会无意间抓住类本身,从而完全阻止了收集!
All that said, I think that it should be possible to implement "managed finalization" using WeakReference. Basically, have your class register a WeakReference to itself and a finalize action with some queue when the object is created. The queue is then monitored by a background thread or timer which calls the appropriate action when it's paired WeakReference gets collected. Of course, you'd have to be careful that your finalize action doesn't inadvertantly hold onto the class itself, thus preventing collection altogether!
推荐答案
您无法将托管对象放置在终结器中,因为它们可能已经终结(或者,如您在此处看到的那样,环境的某些部分可能已经终结了)这意味着如果您包含一个必须显式释放的类,那么您的类也必须显式释放.无法欺骗"并使处置自动进行.不幸的是,在这种情况下,垃圾回收是一个泄漏的抽象.
You can't Dispose managed objects in the finalizer, as they might have already been finalized (or, as you've seen here, portions of the environment may no longer be in the state you're expecting.) This means that if you contain a class which must be Disposed explicitly, your class must also be Disposed explicitly. There's no way to 'cheat' and make the Disposal automatic. Unfortunately, garbage collection is, in cases like this, a leaky abstraction.
这篇关于在Finalizer中处理MemoryCache会抛出AccessViolationException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!