MemoryCache线程安全,locking是必要的?
对于初学者,让我把它扔出去,我知道下面的代码是不是线程安全的(更正:可能)。 我所苦苦挣扎的是find一个实现,一个实际上我可以在testing中失败的实现。 我正在重构一个大的WCF项目,它需要一些(大部分)静态数据caching,并从SQL数据库填充它。 它需要过期,至less每天“刷新”一次,这就是为什么我使用MemoryCache。
我知道,下面的代码不应该是线程安全的,但我不能让它在重负载下失败,并使谷歌search显示实现两个方面的事情复杂化(有和没有锁结合辩论,无论它们是否有必要。
是否可以在multithreading环境中使用MemoryCache的知识,让我明确知道是否需要在适当的位置locking,以便在检索/重新填充期间移除调用(很less被调用但是它的一个要求)不会被抛出。
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; } }
默认的MS提供的MemoryCache
完全是线程安全的。 从MemoryCache
派生的任何自定义实现可能不是线程安全的。 如果您使用普通的MemoryCache
,它是线程安全的。 浏览我的开源分布式caching解决scheme的源代码,了解我如何使用它(MemCache.cs):
https://github.com/haneytron/dache/blob/master/Dache.CacheHost/Storage/MemCache.cs
看看这个链接: http : //msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx
转到页面的最底部(或者search文本“线程安全”)。
你会看见:
^螺纹安全
这种types是线程安全的。
虽然MemoryCache确实是线程安全的,如其他答案已经指定,但它确实有一个共同的multithreading问题 – 如果2线程同时尝试从caching中Get
(或检查Contains
),则两者都将错过caching,生成结果,然后两者将结果添加到caching。
通常这是不可取的 – 第二个线程应该等待第一个完成并使用其结果,而不是两次生成结果。
这是我写LazyCache的原因之一 – MemoryCache的一个友好的包装,解决了这些问题。 这也是在Nuget上 。
正如其他人所说,MemoryCache确实是线程安全的。 存储在其中的数据的线程安全性完全取决于您的使用情况。
引用Reed Copsey关于并发性和ConcurrentDictionary<TKey, TValue>
types的ConcurrentDictionary<TKey, TValue>
post 。 这当然适用于此。
如果两个线程同时调用[GetOrAdd],则可以轻松构build两个TValue实例。
你可以想象,如果TValue
构build成本很高的话,这将会特别糟糕。
为了解决这个问题,你可以非常容易地利用Lazy<T>
,这个巧合的构build起来非常便宜。 这样做确保了如果我们进入multithreading的情况,我们只build立Lazy<T>
多个实例(这很便宜)。
在MemoryCache
的情况下GetOrAdd()
( GetOrCreate()
)将返回相同的,单数的Lazy<T>
到所有线程, Lazy<T>
的“额外”实例被简单地抛弃。
由于Lazy<T>
在调用.Value
之前不会执行任何操作,因此只能创build对象的一个实例。
现在有些代码! 以下是实现上述内容的IMemoryCache
的扩展方法。 它任意设置SlidingExpiration
基于一个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());
为了全部asynchronous执行,我build议使用Stephen Toub在其MSDN 文章中find的出色的AsyncLazy<T>
实现。 它将内置的惰性初始化器Lazy<T>
和promise 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()
的asynchronous版本:
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());