107

手始めに、以下のコードがスレッドセーフではないことを知っていることをそこに投げさせてください(修正:そうかもしれません)。私が苦労しているのは、実際にテストで失敗する可能性のある実装を見つけることです。現在、大規模な WCF プロジェクトをリファクタリングしています。このプロジェクトでは、(ほとんどの場合) 静的データをキャッシュし、SQL データベースからデータを取り込む必要があります。少なくとも 1 日に 1 回は有効期限が切れて「更新」される必要があるため、MemoryCache を使用しています。

以下のコードはスレッドセーフであってはならないことは知っていますが、負荷が高い場合に失敗することはありません。複雑なことに、Google検索では実装が両方の方法で表示されます(ロックの有無にかかわらず、それらが必要かどうかの議論と組み合わされています.

マルチスレッド環境での MemoryCache の知識を持っている人が、remove の呼び出し (めったに呼び出されることはありませんが、その要件です) が取得/再作成中にスローされないように、適切な場所でロックする必要があるかどうかを明確に知ることができますか?

public class MemoryCacheService : IMemoryCacheService
{
    private const string PunctuationMapCacheKey = "punctuationMaps";
    private static readonly ObjectCache Cache;
    private readonly IAdoNet _adoNet;

    static MemoryCacheService()
    {
        Cache = MemoryCache.Default;
    }

    public MemoryCacheService(IAdoNet adoNet)
    {
        _adoNet = adoNet;
    }

    public void ClearPunctuationMaps()
    {
        Cache.Remove(PunctuationMapCacheKey);
    }

    public IEnumerable GetPunctuationMaps()
    {
        if (Cache.Contains(PunctuationMapCacheKey))
        {
            return (IEnumerable) Cache.Get(PunctuationMapCacheKey);
        }

        var punctuationMaps = GetPunctuationMappings();

        if (punctuationMaps == null)
        {
            throw new ApplicationException("Unable to retrieve punctuation mappings from the database.");
        }

        if (punctuationMaps.Cast<IPunctuationMapDto>().Any(p => p.UntaggedValue == null || p.TaggedValue == null))
        {
            throw new ApplicationException("Null values detected in Untagged or Tagged punctuation mappings.");
        }

        // Store data in the cache
        var cacheItemPolicy = new CacheItemPolicy
        {
            AbsoluteExpiration = DateTime.Now.AddDays(1.0)
        };

        Cache.AddOrGetExisting(PunctuationMapCacheKey, punctuationMaps, cacheItemPolicy);

        return punctuationMaps;
    }

    //Go oldschool ADO.NET to break the dependency on the entity framework and need to inject the database handler to populate cache
    private IEnumerable GetPunctuationMappings()
    {
        var table = _adoNet.ExecuteSelectCommand("SELECT [id], [TaggedValue],[UntaggedValue] FROM [dbo].[PunctuationMapper]", CommandType.Text);
        if (table != null && table.Rows.Count != 0)
        {
            return AutoMapper.Mapper.DynamicMap<IDataReader, IEnumerable<PunctuationMapDto>>(table.CreateDataReader());
        }

        return null;
    }
}
4

7 に答える 7

86

MS が提供するデフォルトMemoryCacheは、完全にスレッドセーフです。から派生したカスタム実装は、MemoryCacheスレッド セーフではない可能性があります。すぐに使用できるプレーンを使用している場合MemoryCacheは、スレッドセーフです。オープン ソースの分散キャッシュ ソリューションのソース コードを参照して、その使用方法を確認してください (MemCache.cs)。

https://github.com/haneytron/dache/blob/master/Dache.CacheHost/Storage/MemCache.cs

于 2013-11-22T16:46:58.503 に答える
62

他の回答が指定しているように、MemoryCache は確かにスレッドセーフですが、一般的なマルチスレッドの問題があります。2 つのスレッドが同時にキャッシュGetから取得 (またはチェック) しようとすると、両方がキャッシュを逃し、両方が生成されてしまいます。Contains結果と両方が結果をキャッシュに追加します。

多くの場合、これは望ましくありません。2 番目のスレッドは、結果を 2 回生成するのではなく、最初のスレッドが完了するのを待ってその結果を使用する必要があります。

これが、私がLazyCacheを作成した理由の 1 つです。これは、この種の問題を解決する MemoryCache の使いやすいラッパーです。Nugetでも利用できます。

于 2016-10-17T15:11:34.917 に答える
24

他の人が述べているように、MemoryCache は確かにスレッドセーフです。ただし、その中に格納されているデータのスレッド セーフは、完全に使用者次第です。

Reed Copseyの同時実行性と型に関する素晴らしい投稿から引用しますConcurrentDictionary<TKey, TValue>。これはもちろんここに当てはまります。

2 つのスレッドがこの [GetOrAdd] を同時に呼び出すと、TValue の 2 つのインスタンスを簡単に作成できます。

TValue建設に費用がかかる場合、これは特に悪いことであると想像できます。

これを回避するには、Lazy<T>非常に簡単に活用できます。偶然にも、非常に安価に構築できます。これを行うことで、マルチスレッドの状況になった場合に、Lazy<T>(安価な) の複数のインスタンスのみを構築することが保証されます。

GetOrAdd()(GetOrCreate()の場合MemoryCache) は、すべてのスレッドに対して特異な同じものを返しLazy<T>ます。 の「余分な」インスタンスLazy<T>は単純に破棄されます。

Lazy<T>が呼び出されるまで何もしないため.Value、オブジェクトのインスタンスは 1 つしか構築されません。

それでは、いくつかのコードを見てみましょう。IMemoryCache以下は、上記を実装する拡張メソッドです。メソッドparamSlidingExpirationに基づいて任意に設定です。int secondsただし、これはニーズに基づいて完全にカスタマイズ可能です。

これは .netcore2.0 アプリに固有のものであることに注意してください

public static T GetOrAdd<T>(this IMemoryCache cache, string key, int seconds, Func<T> factory)
{
    return cache.GetOrCreate<T>(key, entry => new Lazy<T>(() =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(seconds);

        return factory.Invoke();
    }).Value);
}

呼び出すには:

IMemoryCache cache;
var result = cache.GetOrAdd("someKey", 60, () => new object());

これをすべて非同期で実行するには、MSDNの彼の記事にあるStephen Toub の優れたAsyncLazy<T>実装を使用することをお勧めします。組み込みの遅延初期化子と promiseを組み合わせたもの:Lazy<T>Task<T>

public class AsyncLazy<T> : Lazy<Task<T>>
{
    public AsyncLazy(Func<T> valueFactory) :
        base(() => Task.Factory.StartNew(valueFactory))
    { }
    public AsyncLazy(Func<Task<T>> taskFactory) :
        base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap())
    { }
}   

の非同期バージョンGetOrAdd():

public static Task<T> GetOrAddAsync<T>(this IMemoryCache cache, string key, int seconds, Func<Task<T>> taskFactory)
{
    return cache.GetOrCreateAsync<T>(key, async entry => await new AsyncLazy<T>(async () =>
    { 
        entry.SlidingExpiration = TimeSpan.FromSeconds(seconds);

        return await taskFactory.Invoke();
    }).Value);
}

そして最後に、次のように呼び出します。

IMemoryCache cache;
var result = await cache.GetOrAddAsync("someKey", 60, async () => new object());
于 2017-08-22T19:43:40.890 に答える
11

このリンクをチェックしてください: http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx

ページの一番下に移動します (または、「スレッド セーフ」というテキストを検索します)。

以下が表示されます。

^ スレッドの安全性

この型はスレッドセーフです。

于 2013-11-22T20:04:25.447 に答える
3

.Net 2.0 の問題に対処するために、サンプル ライブラリをアップロードしました。

このレポを見てください:

RedisLazyCache

私はRedisキャッシュを使用していますが、接続文字列が欠落している場合はフェールオーバーまたはメモリキャッシュのみです。

これは、LazyCache ライブラリに基づいており、コールバックの実行に非常にコストがかかる場合に、マルチスレッドがデータのロードと保存を特別に試行する場合に、書き込み用のコールバックの単一実行を保証します。

于 2018-04-28T00:00:38.510 に答える