【发布时间】:2014-02-11 16:46:15
【问题描述】:
我认为这段代码存在并发问题:
const string CacheKey = "CacheKey";
static string GetCachedData()
{
string expensiveString =null;
if (MemoryCache.Default.Contains(CacheKey))
{
expensiveString = MemoryCache.Default[CacheKey] as string;
}
else
{
CacheItemPolicy cip = new CacheItemPolicy()
{
AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
};
expensiveString = SomeHeavyAndExpensiveCalculation();
MemoryCache.Default.Set(CacheKey, expensiveString, cip);
}
return expensiveString;
}
并发问题的原因是多个线程可以获取一个空键然后尝试将数据插入缓存。
使此代码并发证明的最短和最简洁的方法是什么?我喜欢在我的缓存相关代码中遵循一个好的模式。在线文章的链接会很有帮助。
更新:
我根据@Scott Chamberlain 的回答想出了这段代码。任何人都可以找到任何性能或并发问题吗? 如果这可行,它将节省许多代码行和错误。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Caching;
namespace CachePoc
{
class Program
{
static object everoneUseThisLockObject4CacheXYZ = new object();
const string CacheXYZ = "CacheXYZ";
static object everoneUseThisLockObject4CacheABC = new object();
const string CacheABC = "CacheABC";
static void Main(string[] args)
{
string xyzData = MemoryCacheHelper.GetCachedData<string>(CacheXYZ, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);
string abcData = MemoryCacheHelper.GetCachedData<string>(CacheABC, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);
}
private static string SomeHeavyAndExpensiveXYZCalculation() {return "Expensive";}
private static string SomeHeavyAndExpensiveABCCalculation() {return "Expensive";}
public static class MemoryCacheHelper
{
public static T GetCachedData<T>(string cacheKey, object cacheLock, int cacheTimePolicyMinutes, Func<T> GetData)
where T : class
{
//Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
T cachedData = MemoryCache.Default.Get(cacheKey, null) as T;
if (cachedData != null)
{
return cachedData;
}
lock (cacheLock)
{
//Check to see if anyone wrote to the cache while we where waiting our turn to write the new value.
cachedData = MemoryCache.Default.Get(cacheKey, null) as T;
if (cachedData != null)
{
return cachedData;
}
//The value still did not exist so we now write it in to the cache.
CacheItemPolicy cip = new CacheItemPolicy()
{
AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(cacheTimePolicyMinutes))
};
cachedData = GetData();
MemoryCache.Default.Set(cacheKey, cachedData, cip);
return cachedData;
}
}
}
}
}
【问题讨论】:
-
你为什么不用
ReaderWriterLockSlim? -
我同意 DarthVader... 我认为您倾向于
ReaderWriterLockSlim... 但我也会使用 this 技术来避免try-finally声明。 -
对于您的更新版本,我不会再锁定单个 cacheLock,而是锁定每个键。这可以通过
Dictionary<string, object>轻松完成,其中密钥与您在MemoryCache中使用的密钥相同,字典中的对象只是您锁定的基本Object。但是,话虽如此,我建议您通读 Jon Hanna 的回答。如果没有适当的分析,您可能会通过锁定来减慢程序的速度,而不是让两个SomeHeavyAndExpensiveCalculation()实例运行并丢弃一个结果。 -
在我看来,在获得昂贵的缓存值之后创建 CacheItemPolicy 会更准确。在最坏的情况下,例如创建一个需要 21 分钟才能返回“昂贵字符串”(可能包含 PDF 报告的文件名)的摘要报告,在返回之前就已经“过期”了。
-
@Wonderbird 好点,我更新了我的答案。
标签: c# .net multithreading memorycache