2013-11-22 20 views
40

まずは、以下のコードがスレッドセーフではないことを知っていることをまず捨ててください(訂正はあります)。私が苦労しているのは、実際にテストで失敗するようなインプリメンテーションを見つけることです。私は大規模なWCFプロジェクトを現在リファクタリングしていますが、静的なデータをキャッシュし、SQLデータベースからそのデータを読み込む必要があります。私はMemoryCacheを使用しているので、少なくとも1日に1回は有効期限を切ってリフレッシュする必要があります。MemoryCacheスレッドの安全性、ロックは必要ですか?

以下のコードはスレッドセーフではないはずですが、重い負荷で失敗したり、問題を複雑にすることはできません。Google検索では両方の方法が実装されています(ロックの有無にかかわらず、

マルチスレッド環境でMemoryCacheを知っている人が、私が適切な場所にロックする必要があるかどうかを確実に知ることができます(削除する呼び出しはめったに呼び出されませんが要件はありません)検索/再登録

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; 
    } 
} 
+0

ObjectCacheは、スレッドセーフです、私はあなたのクラスが失敗するとは思わない。 http://msdn.microsoft.com/en-us/library/system.runtime.caching.objectcache(v=vs.110).aspxあなたは同時にデータベースに行くかもしれませんが、それはより多くのCPUを使用します必要以上に –

+0

ObjectCacheはスレッドセーフですが、その実装はそうでないかもしれません。したがって、MemoryCacheの質問。 – Haney

答えて

34

デフォルトのMS-pro MemoryCacheはスレッドセーフです。 MemoryCacheから派生したカスタム実装は、スレッドセーフではない可能性があります。普通のMemoryCacheをそのまま使用しているのであれば、スレッドセーフです。私は(MemCache.cs)それを使用する方法を確認するために私のオープンソースの分散キャッシング・ソリューションのソースコードを見る:

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

+1

David、 上記の非常に単純な例のクラスでは、Get()を呼び出す処理中に別のスレッドが存在する場合、.Remove()の呼び出しは実際にはスレッドセーフであることを確認してください。私はリフレクターを使って深く掘り下げるべきだと思うが、そこには多くの相反する情報がある。 –

+7

スレッドセーフですが、競合状態になりがちです...あなたのRemoveがRemoveの前に発生した場合、データはGetに返されます。削除が最初に行われた場合、削除されません。これは、データベース上のダーティリードとよく似ています。 – Haney

7

このリンクをチェックアウト:ページの一番下にhttp://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx

ゴー(または「スレッド安全性」というテキストを検索してください)。

あなたが表示されます。

^スレッドセーフ

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

+4

私はMSDNの "Thread Safe"の定義をかなり前から個人的な経験に基づいて信頼していませんでした。ここでは、良い読書です: [リンク](http://stackoverflow.com/questions/3137931/msdn-what-is-thread-safety) –

+2

その投稿は、私が上記で提供したリンクとは少し異なります。この区別は、私が提供したリンクがスレッドの安全宣言には何の警告も与えていない点で非常に重要です。私はまた、非常に高いボリューム(毎分何百万のキャッシュヒット)で 'MemoryCache.Default'を使用して個人的な経験をしていますが、まだスレッドの問題はありません。 – EkoostikMartin

10

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

多くの場合、これは望ましくないことです。第2スレッドは、最初に完了して結果を2回生成するのではなく、結果を使用するのを待つ必要があります。

これは、私がLazyCache - これらの問題を解決するMemoryCacheのフレンドリーなラッパーを書いた理由の1つでした。それはNugetでも利用可能です。

4

他にも述べたように、MemoryCacheは本当にスレッドセーフです。しかし、その中に格納されているデータのスレッドの安全性は、それを使用することによってまったく決まります。

Reed Copseyは、彼の素晴らしいところではpostで、並行性に関してはConcurrentDictionary<TKey, TValue>と書かれています。もちろんここに当てはまります。

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

TValueが構築するのが高価な場合、これが特に悪いと想像することができます。

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

GetOrAdd()MemoryCacheの場合GetOrCreate())はすべてのスレッドに同じ、単数Lazy<T>を返します、Lazy<T>の「余分な」インスタンスは、単に捨てられます。

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

コードがあります。以下は上記を実装するIMemoryCacheの拡張メソッドです。任意には、int secondsメソッドのパラメータに基づいてSlidingExpirationを設定しています。しかし、これはあなたのニーズに基づいて完全にカスタマイズ可能です。

注これは呼び出すには.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()); 

をすべて非同期にこれを実行するには、私がで見つかったStephen Toub's優れたAsyncLazy<T>実装を使用することをお勧めしますMSDNのarticleこれは約束Task<T>に組み込み怠惰な初期化子Lazy<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()); 
関連する問題