編集 追加の詳細については、質問の下部にある編集メモを参照してください。
元の質問
MemoryCache
内部で .NET クラスのインスタンスを作成して保持する CacheWrapper クラスがあります。
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
これにより、インスタンスの破棄が試みられます。ここで問題が発生します。IIS からいくつかの構成情報を読み込もうとしているようですがDispose
、失敗します (おそらくアプリ ドメインをアンロードしている最中なので、よくわかりません。私が持っているスタック ダンプは次のとおりです。
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
特に、廃棄時にバッファをフラッシュする必要があるため、私の場合と同様のシナリオに直面するのケースを取り上げています。記事の内容は次のとおりです。
一般に、ファイナライザーは管理対象オブジェクトにアクセスできません。ただし、かなり複雑なソフトウェアには、シャットダウン ロジックのサポートが必要です。Windows.Forms 名前空間は、正常なシャットダウンを開始する Application.Exit でこれを処理します。ライブラリ コンポーネントを設計する場合、既存の論理的に類似した IDisposable と統合されたシャットダウン ロジックをサポートする方法があると便利です (これにより、組み込みの言語サポートなしで IShutdownable インターフェイスを定義する必要がなくなります)。これは通常、IDisposable.Dispose が呼び出された場合は正常なシャットダウンをサポートし、そうでない場合は中止的なシャットダウンをサポートすることによって行われます。ファイナライザーを使用して、可能な限り正常なシャットダウンを実行できればさらに良いでしょう。
マイクロソフトもこの問題に直面しました。StreamWriter クラスは Stream オブジェクトを所有しています。StreamWriter.Close はそのバッファーをフラッシュしてから、Stream.Close を呼び出します。ただし、StreamWriter が閉じられていない場合、そのファイナライザーはそのバッファーをフラッシュできません。Microsoft は StreamWriter にファイナライザーを提供しないことでこの問題を「解決」し、プログラマーがデータの欠落に気付き、エラーを推測できるようにしました。これは、シャットダウン ロジックの必要性を示す完璧な例です。
そうは言っても、WeakReference を使用して「マネージド ファイナライズ」を実装できるはずだと思います。基本的に、オブジェクトが作成されたときに、自分自身への WeakReference といくつかのキューでのファイナライズ アクションをクラスに登録させます。キューはバックグラウンド スレッドまたはタイマーによって監視され、ペアになっている WeakReference が収集されると、適切なアクションが呼び出されます。もちろん、ファイナライズ アクションが誤ってクラス自体を保持して、コレクションが完全に妨げられないように注意する必要があります。