web-dev-qa-db-ja.com

System.Runtime.Caching.MemoryCacheをクリアする方法

_System.Runtime.Caching.MemoryCache_を使用して、無期限のアイテムを保持します。ただし、キャッシュ全体をクリアする機能が必要な場合があります。それ、どうやったら出来るの?

キャッシュを列挙できるかどうかについて同様の質問 here を尋ねましたが、列挙中に同期する必要があるため、これは悪い考えです。

.Trim(100)を使用しようとしましたが、まったく機能しません。

私はすべてのキーのリストをLinq経由で取得しようとしましたが、アイテムを1つずつ削除すると簡単に競合状態になる可能性があるため、最初に戻りました。

すべてのキーを保存し、各キーに対して.Remove(key)を発行することを考えましたが、暗黙の競合状態もあるため、キーのリストへのアクセスをロックする必要があります。再び厄介。

その後、キャッシュ全体で.Dispose()を呼び出すことができると考えましたが、実装方法が原因でこれが最善のアプローチであるかどうかはわかりません。

ChangeMonitorsを使用することは私の設計のオプションではなく、このような些細な要件のために必要以上に複雑です。

それでは、キャッシュを完全にクリアするにはどうすればよいですか?

37
Peter Marks

MemoryCacheのデフォルトメンバを使用できるようにしたい場合は、Disposeを呼び出さないでください。

キャッシュの状態は、キャッシュが破棄されたことを示すように設定されます。キャッシュエントリを追加、削除、または取得するメソッドなど、キャッシュの状態を変更するパブリックキャッシュメソッドを呼び出そうとすると、予期しない動作が発生する可能性があります。たとえば、キャッシュが破棄された後にSetメソッドを呼び出すと、no-opエラーが発生します。キャッシュからアイテムを取得しようとすると、Getメソッドは常にNothingを返します。 http://msdn.Microsoft.com/en-us/library/system.runtime.caching.memorycache.dispose.aspx

トリムについては、動作するはずです:

Trimプロパティは、最初に絶対有効期限またはスライド式有効期限を超えたエントリを削除します。削除されたアイテムに対して登録されたコールバックには、期限切れの削除された理由が渡されます。

期限切れのエントリを削除しても指定したトリム率に達するには不十分な場合、追加のエントリは、要求されたトリム率に達するまで最小使用頻度(LRU)アルゴリズムに基づいてキャッシュから削除されます。

しかし、他の2人のユーザーが同じページで機能しないと報告したため、Remove() http://msdn.Microsoft.com/en-us/library/system.runtime.caching.memorycache.trimで立ち往生していると思います.aspx

Updateしかし、シングルトンであるか、複数のインスタンスを持つことは安全でないという言及はないので、参照を上書きできるはずです。

ただし、デフォルトインスタンスからメモリを解放する必要がある場合は、手動でメモリをクリアするか、破棄して永続的に破棄する必要があります(使用不可にレンダリングします)。

あなたの質問に基づいて、自由に内部的に破棄できるMemorycacheを返す独自のシングルトンを課すクラスを作成することができます。キャッシュの性質であること:-)

17
stefan

最初はこれに苦労していました。 MemoryCache.Default.Trim(100)は機能しません(説明のとおり)。トリムは最良の試みであるため、キャッシュに100個のアイテムがあり、Trim(100)を呼び出すと、最も使用されていないものが削除されます。

トリムは削除されたアイテムの数を返し、ほとんどの人はすべてのアイテムを削除することを期待しています。

このコードは、MemoryCache.Defaultを使用したxUnitテストで、MemoryCacheからすべてのアイテムを削除します。 MemoryCache.Defaultはデフォルトのリージョンです。

foreach (var element in MemoryCache.Default)
{
    MemoryCache.Default.Remove(element.Key);
}
33

これは私が取り組んでいたもののために作ったものです...

public void Flush()
{
    List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
    foreach (string cacheKey in cacheKeys)
    {
        MemoryCache.Default.Remove(cacheKey);
    }
}
8
Yasser

@stefanの回答の詳細は、原則の詳細です。ここに私がやる方法があります。

キャッシュが破棄された後、再作成される前にキャッシュにアクセスするクライアントコードの競合状態を回避するために、キャッシュの再作成中にキャッシュへのアクセスを同期する必要があります。

この同期を回避するには、アダプタークラス(MemoryCacheをラップする)でこれを実行します。

public void clearCache() {
  var oldCache = TheCache;
  TheCache = new MemoryCache("NewCacheName", ...);
  oldCache.Dispose();
  GC.Collect();
}

このように、TheCacheは常に非廃棄状態にあり、同期は不要です。

2
Peter Marks

私はこれが古い質問であることを知っていますが、私が遭遇した最良の選択肢は

既存のMemoryCacheを破棄し、新しいMemoryCacheオブジェクトを作成します。 https://stackoverflow.com/a/4183319/880642

答えは、実際にスレッドセーフな方法でこれを行うコードを提供しません。ただし、これは Interlocked.Exchange を使用して実現できます。

var oldCache = Interlocked.Exchange(ref _existingCache, new MemoryCache("newCacheName"));
oldCache.Dispose();

これにより、既存のキャッシュが新しいキャッシュと交換され、元のキャッシュでDisposeを安全に呼び出すことができます。これにより、キャッシュ内のアイテムを列挙する必要がなくなり、使用中のキャッシュを破棄することで競合状態が発生します。

2
Rondel

this 投稿、および具体的には Thomas F. Abraham 投稿の回答をご覧ください。キャッシュ全体または名前付きサブセットをクリアできるソリューションがあります。

ここで重要なのは:

// Cache objects are obligated to remove entry upon change notification.
base.OnChanged(null);

私はこれを自分で実装しましたが、すべてうまくいくようです。

0
Jowen

私もこの問題に遭遇しました。 .Dispose()は、私が予想したものとはまったく異なることをしました。

代わりに、コントローラークラスに静的フィールドを追加しました。この動作を回避するためにデフォルトのキャッシュを使用しませんでしたが、プライベートキャッシュを作成しました(それを呼び出したい場合)。そのため、私の実装は次のようになりました。

public class MyController : Controller
{

    static MemoryCache s_cache = new MemoryCache("myCache");

    public ActionResult Index()
    {

        if (conditionThatInvalidatesCache)
        {
            s_cache = new MemoryCache("myCache");
        }

        String s = s_cache["key"] as String;

        if (s == null)
        {
            //do work
            //add to s_cache["key"]
        }

        //do whatever next
    }
}
0
Richard Lander