3

C# での MemoryCache のクリアに関する質問と回答を読みました。次のような多くの推奨事項がありました:1.キャッシュを列挙し、すべてのアイテムを削除します-他の人によると、これは良くありません.列挙子を取得すると全体がロックされ、ドキュメントの一部から引用すると、あらゆる種類の黙示録が発生します. 、見つかりませんでした。警告が表示され、再現できませんでした。とにかく、これは非常に効率的な解決策だとは思いません。

  1. キーを別のコレクションに保存し、それを繰り返して、キャッシュからアイテムを削除します。- これを除けば、あまりスレッド セーフとは言えません。効率的とも言えません。

  2. 古いキャッシュを破棄し、新しいキャッシュを作成する - これは良いことのように思えますが、古いキャッシュへの既存の参照が問題を引き起こす可能性があるという明らかな問題が頭に浮かび、いくつかのコメントで指摘されています。もちろん、アクションの順序は重要です。古いものの参照を保存し、古いものの代わりに新しいものを作成し、古いものを破棄する必要があります-誰もがこの小さなニュアンスに気付いていないようです.

ならどうしよう?私の同僚の 1 人が、私の問題に MemoryCache を使用することを提案しました。彼はキャッシュ オブジェクトを別のクラスにラップしました。このクラスには、キーを取得する (必要に応じてデータベースからロードする)、キーを削除する、またはキャッシュをクリアするオプションがあります。 . 最初の 2 つは今のところ重要ではありませんが、3 番目は興味深いものです。これには、「自分の実装以外の MemoryCache オブジェクトへの追加の参照がないことが保証されている」という行に沿って、3 番目のソリューションを使用しました。したがって、関連するコードは次のとおりです。

コンストラクタ:

public MyCache()
{
    _cache = new MemoryCache("MyCache");
}

キャッシュの消去:

public void ClearCacheForAllUsers()
{
    var oldCache = _cache;
    _cache = new MemoryCache("MyCache");
    oldCache.Dispose();
}

_cache はプライベートな MemoryCache オブジェクトです。

これにより、マルチスレッド環境で問題が発生する可能性はありますか? read と dispose を並行して呼び出すことに懸念があります。同時読み取りを許可する何らかのロック メカニズムを実装する必要がありますが、キャッシュのクリア関数は現在の読み取りがキャッシュで終了するまで待機しますか?

はい、これを実装する必要がありますが、それに到達する前にいくつかの洞察を得たいと思います。

ロバート

Vooの答えのEDIT 1: 「ラッパー」(MyCache)の外にいる人は誰も_cacheオブジェクトへの参照を取得しないことが保証されています。私の心配は次のようなものです。

T1:
MyCache.ClearAll()
    var oldCache = _cache
T2:
MyCache.GetOrReadFromDb("stuff")
T1:
    _cache=new MemoryCache("MyCache")
    oldCache.Dispose()
T2:
    *something bad*

古いキャッシュをまだ使用しているT2スレッドは別として、これは好ましくありませんが、私が住むことができるものは、Getメソッドが破棄された状態で古いキャッシュに何らかの形でアクセスしたり、データなしで新しいキャッシュを読み取ったりする状況がある可能性があります?

次のような GetOrReadFromDb 関数を想像してください。

public object GetOrReadFromDb(string key)
{
    if(_cache[key]==null)
    {
        //Read stuff from DB, and insert into the cache
    }
    return _cache[key];
}

たとえば、db からの読み取り直後、および _chache[key] 値で戻る前に、読み取りスレッドから制御が奪われ、クリアに渡される可能性があり、それが問題を引き起こす可能性があると思います。それは本当の問題ですか?

4

2 に答える 2

2

あなたのソリューションの問題は、醜いロックまたは他のスレッド同期ソリューションを必要とする可能性のある競合状態を導入することです。

代わりに、組み込みのソリューションを使用してください。

カスタムChangeMonitorクラスを使用して、アイテムをキャッシュからクリアできます。を呼び出すときに、設定した のプロパティにChangeMonitorインスタンスを追加できます。ChangeMonitorsCacheItemPolicyMemoryCache.Set

例えば:

class MyCm : ChangeMonitor
{
    string uniqueId = Guid.NewGuid().ToString();

    public MyCm()
    {
        InitializationComplete();
    }

    protected override void Dispose(bool disposing) { }

    public override string UniqueId
    {
        get { return uniqueId; }
    }

    public void Stuff()
    {
        this.OnChanged(null);
    }
}

使用例:

        var cache = new MemoryCache("MyFancyCache");
        var obj = new object();
        var cm = new MyCm();
        var cip = new CacheItemPolicy();
        cip.ChangeMonitors.Add(cm);

        cache.Set("MyFancyObj", obj, cip);

        var o = cache.Get("MyFancyObj");
        Console.WriteLine(o != null);

        o = cache.Get("MyFancyObj");
        Console.WriteLine(o != null);

        cm.Stuff();

        o = cache.Get("MyFancyObj");
        Console.WriteLine(o != null);

        o = cache.Get("MyFancyObj");
        Console.WriteLine(o != null);
于 2014-08-13T08:14:53.283 に答える
0

クラスを使用したことはありませんMemoryCacheが、明らかな競合状態が発生しています。別のスレッド T2 が実行されている間に、スレッド T1 がGetElement(またはそれを呼び出すものは何でも) 呼び出すことができますClearCacheForAllUsers

次に、次のことが発生する可能性があります。

T1:
reg = _cache
T2:
oldCache = _cache
_cache = new MemoryCache("MyCache")
oldCache.Dispose()
T1:
reg.Get()   // Ouch bad! Dispose tells us this results in "unexpected behavior"

簡単な解決策は、同期を導入することです。

ロックが受け入れられない場合は、いくつかの巧妙なトリックを使用できますが、最も簡単な解決策は を使用することFinalizeです。確かに、一般的には悪い考えですが、キャッシュが行うことはメモリを使い果たすことだけであるため、キャッシュがメモリ不足の状態でのみ収集されても害はないと仮定すると、GC に心配させることで競合状態を心配する必要がなくなります。それについて。ただし、_cache揮発性でなければならないことに注意してください。

別の可能なアプローチは、実装の詳細として、キャッシュが破棄された後にGet明らかに常に戻るというものです。nullその場合、これらのまれな状況を除いて、ほとんどの場合ロックを回避できます。これは実装定義であり、個人的にはそのような小さな詳細に依存したくありません。

于 2014-08-12T17:35:14.287 に答える