C# MemoryCache线程安全,是否需要锁定?
首先,让我把它扔出去,我知道下面的代码不是线程安全的(更正:可能是)。我正在努力寻找一个在测试中可能失败的实现。我现在正在重构一个大型WCF项目,该项目需要缓存一些(大部分)静态数据,并从SQL数据库填充这些数据。它需要过期并每天至少“刷新”一次,这就是我使用MemoryCache的原因 我知道下面的代码不应该是线程安全的,但我不能让它在重载情况下失败,也不能让事情变得复杂。谷歌搜索显示了两种实现方式(带锁和不带锁,并讨论它们是否必要) 如果有人了解多线程环境中的MemoryCache,请告诉我是否需要在适当的情况下锁定,以便在检索/重新填充期间不会抛出删除调用(虽然很少调用,但这是一项要求)C# MemoryCache线程安全,是否需要锁定?,c#,multithreading,wcf,memorycache,C#,Multithreading,Wcf,Memorycache,首先,让我把它扔出去,我知道下面的代码不是线程安全的(更正:可能是)。我正在努力寻找一个在测试中可能失败的实现。我现在正在重构一个大型WCF项目,该项目需要缓存一些(大部分)静态数据,并从SQL数据库填充这些数据。它需要过期并每天至少“刷新”一次,这就是我使用MemoryCache的原因 我知道下面的代码不应该是线程安全的,但我不能让它在重载情况下失败,也不能让事情变得复杂。谷歌搜索显示了两种实现方式(带锁和不带锁,并讨论它们是否必要) 如果有人了解多线程环境中的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;
}
}
公共类MemoryCacheService:IMemoryCacheService
{
私有常量字符串标点符号MapCacheKey=“标点符号映射”;
私有静态只读对象缓存;
私有只读IAdoNet;
静态内存缓存服务()
{
Cache=MemoryCache.Default;
}
公共内存缓存服务(IAdoNet adoNet)
{
_adoNet=adoNet;
}
public void clearportionmaps()
{
Cache.Remove(标点映射cachekey);
}
public IEnumerable GetPercentrationMaps()
{
if(Cache.Contains(标点MapCacheKey))
{
return(IEnumerable)Cache.Get(标点MapCacheKey);
}
var percentrationmaps=getpercentrationmappings();
if(标点映射==null)
{
抛出新的ApplicationException(“无法从数据库检索标点符号映射”);
}
if(标点映射.Cast().Any(p=>p.UntagedValue==null | | p.TaggedValue==null))
{
抛出新的ApplicationException(“在未标记或标记的标点符号映射中检测到空值”);
}
//将数据存储在缓存中
var cacheItemPolicy=新的cacheItemPolicy
{
AbsoluteExpiration=DateTime.Now.AddDays(1.0)
};
AddOrGetExisting(标点符号MapCacheKey、标点符号Maps、cacheItemPolicy);
返回标点图;
}
//转到oldschool ADO.NET以打破对实体框架的依赖,并需要注入数据库处理程序以填充缓存
private IEnumerable Get标点映射()
{
var table=_adoNet.ExecuteSelectCommand(“从[dbo].[标点映射器]]中选择[id]、[TaggedValue]、[UntagedValue]”,CommandType.Text);
if(table!=null&&table.Rows.Count!=0)
{
返回AutoMapper.Mapper.DynamicMap(table.CreateDataReader());
}
返回null;
}
}
提供的默认MSMemoryCache
完全是线程安全的。从MemoryCache
派生的任何自定义实现可能都不是线程安全的。如果您使用的是现成的普通MemoryCache
,它是线程安全的。请浏览我的开源分布式缓存解决方案的源代码,了解我是如何使用它的(MemCache.cs):
查看此链接:
转到页面的最底部(或搜索文本“线程安全”)
你会看到:
^线程安全性
这种类型是线程安全的
虽然MemoryCache确实是线程安全的,正如其他答案所指定的那样,但它确实存在一个常见的多线程问题—如果两个线程尝试从中获取(或检查
包含)同时删除缓存,则两者都将丢失缓存,并最终生成结果,然后将结果添加到缓存中
通常这是不可取的-第二个线程应该等待第一个线程完成并使用其结果,而不是生成两次结果
这就是我写这篇文章的原因之一——MemoryCache上的友好包装解决了这类问题。正如其他人所说,MemoryCache确实是线程安全的。然而,存储在其中的数据的线程安全性完全取决于您对它的使用
引用他关于并发性和ConcurrentDictionary
类型的绝妙见解。这当然适用于这里
如果两个线程同时调用这个[GetOrAdd],那么TValue的两个实例就可以很容易地构造出来
您可以想象,如果TValue
的构造成本很高,那么这将特别糟糕
为了解决这个问题,您可以非常轻松地利用Lazy
,这恰好是非常便宜的构造方法。这样做可以确保在进入多线程环境时,我们只构建了Lazy
的多个实例(这很便宜)
GetOrAdd()
(GetOrCreate()
在MemoryCache
的情况下)将向所有线程返回相同的、单数的Lazy
,而Lazy
的“额外”实例将被简单地丢弃
由于在调用.Value
之前,Lazy
不会执行任何操作,因此只会构造对象的一个实例
下面是实现上述功能的IMemoryCache
的扩展方法。它根据int seconds
方法参数任意设置SlidingExpiration
。但这完全可以根据您的需要定制
注意:这是特定于.netcore2.0应用程序的
为了异步执行这一切,我建议使用在MSDN上的his中找到的优秀的AsyncLazy
实现。它将内置的惰性初始值设定项lazy
与promise任务相结合:
最后,打电话:
IMemoryCache cache;
var result = await cache.GetOrAddAsync("someKey", 60, async () => new object());
刚刚上载了示例库以解决.Ne的问题
IMemoryCache cache;
var result = cache.GetOrAdd("someKey", 60, () => new object());
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())
{ }
}
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());
class Program
{
static async Task Main(string[] args)
{
var cache = new MemoryCache(new MemoryCacheOptions());
var tasks = new List<Task>();
var counter = 0;
for (int i = 0; i < 10; i++)
{
var loc = i;
tasks.Add(Task.Run(() =>
{
var x = GetOrAdd(cache, "test", TimeSpan.FromMinutes(1), () => Interlocked.Increment(ref counter));
Console.WriteLine($"Interation {loc} got {x}");
}));
}
await Task.WhenAll(tasks);
Console.WriteLine("Total value creations: " + counter);
Console.ReadKey();
}
public static T GetOrAdd<T>(IMemoryCache cache, string key, TimeSpan expiration, Func<T> valueFactory)
{
return cache.GetOrCreate(key, entry =>
{
entry.SetSlidingExpiration(expiration);
return new Lazy<T>(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
}).Value;
}
}
Interation 6 got 8
Interation 7 got 6
Interation 2 got 3
Interation 3 got 2
Interation 4 got 10
Interation 8 got 9
Interation 5 got 4
Interation 9 got 1
Interation 1 got 5
Interation 0 got 7
Total value creations: 10
public static T GetOrSetValueSafe<T>(IMemoryCache cache, string key, TimeSpan expiration,
Func<T> valueFactory)
{
if (cache.TryGetValue(key, out Lazy<T> cachedValue))
return cachedValue.Value;
cache.GetOrCreate(key, entry =>
{
entry.SetSlidingExpiration(expiration);
return new Lazy<T>(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
});
return cache.Get<Lazy<T>>(key).Value;
}
Interation 4 got 1
Interation 9 got 1
Interation 1 got 1
Interation 8 got 1
Interation 0 got 1
Interation 6 got 1
Interation 7 got 1
Interation 2 got 1
Interation 5 got 1
Interation 3 got 1
Total value creations: 1
private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1);
await _cacheLock.WaitAsync();
var data = await _cache.GetOrCreateAsync(key, entry => ...);
_cacheLock.Release();