我编写了一个通用的Cache类,用于返回一个内存中的对象,并且只是偶尔评估src(一个IQueryable或一个返回IQueryable的函数)。它在我的应用程序的几个地方使用,通过实体框架获取大量数据是昂贵的。
它被称为
//class level
private static CachedData<Foo> fooCache= new CachedData<Foo>();
//method level
var results = fooCache.GetData("foos", fooRepo.Include("Bars"));
虽然它似乎在测试中运行正常,但在繁忙的Web服务器上运行时,我看到一些问题“集合已被修改;枚举操作可能无法执行”。消耗结果的代码中的错误。
这必须是因为一个线程正在覆盖锁内的结果对象,而另一个线程正在锁外使用它们。
我猜我唯一的解决方案是将结果的副本返回给每个使用者而不是原始的,并且我不能允许在Fetch锁内部发生复制,但是可以同时发生多个副本。
有人可以建议更好的方法,或者帮助锁定策略吗?
public class CachedData<T> where T:class
{
private static Dictionary<string, IEnumerable<T>> DataCache { get; set; }
public static Dictionary<string, DateTime> Expire { get; set; }
public int TTL { get; set; }
private object lo = new object();
public CachedData()
{
TTL = 600;
Expire = new Dictionary<string, DateTime>();
DataCache = new Dictionary<string, IEnumerable<T>>();
}
public IEnumerable<T> GetData(string key, Func<IQueryable<T>> src)
{
var bc = brandKey(key);
if (!DataCache.ContainsKey(bc)) Fetch(bc, src);
if (DateTime.Now > Expire[bc]) Fetch(bc, src);
return DataCache[bc];
}
public IEnumerable<T> GetData(string key, IQueryable<T> src)
{
var bc = brandKey(key);
if ((!DataCache.ContainsKey(bc)) || (DateTime.Now > Expire[bc])) Fetch(bc, src);
return DataCache[bc];
}
private void Fetch(string key, IQueryable<T> src )
{
lock (lo)
{
if ((!DataCache.ContainsKey(key)) || (DateTime.Now > Expire[key])) ExecuteFetch(key, src);
}
}
private void Fetch(string key, Func<IQueryable<T>> src)
{
lock (lo)
{
if ((!DataCache.ContainsKey(key)) || (DateTime.Now > Expire[key])) ExecuteFetch(key, src());
}
}
private void ExecuteFetch(string key, IQueryable<T> src)
{
if (!DataCache.ContainsKey(key)) DataCache.Add(key, src.ToList());
else DataCache[key] = src.ToList();
if (!Expire.ContainsKey(key)) Expire.Add(key, DateTime.Now.AddSeconds(TTL));
else Expire[key] = DateTime.Now.AddSeconds(TTL);
}
private string brandKey(string key, int? brandid = null)
{
return string.Format("{0}/{1}", brandid ?? Config.BrandID, key);
}
}
答案 0 :(得分:1)
我通常使用ConcurrentDictionary<TKey, Lazy<TValue>>
。这会给你一把锁。它使策略在获取可行时保持锁定。这也避免了缓存加盖。它保证每个密钥只能进行一次评估。 Lazy<T>
完全自动完成锁定。
关于过期逻辑:您可以设置一个定时器,每隔X秒清除字典(或完全重写字典)。