缓存类和已修改的集合
本文关键字:集合 修改 缓存 | 更新日期: 2023-09-27 18:22:03
我编写了一个通用的Cache类,旨在返回内存中的对象,偶尔只计算src(一个IQueryable,或一个返回IQueryaable的函数)。它在我的应用程序中的几个地方使用,通过实体框架获取大量数据的成本很高。
它被称为
//class level
private static CachedData<Foo> fooCache= new CachedData<Foo>();
//method level
var results = fooCache.GetData("foos", fooRepo.Include("Bars"));
尽管它在测试中似乎工作正常,但在繁忙的web服务器上运行时,我看到"Collection已被修改;枚举操作可能无法执行"的一些问题。使用结果的代码中存在错误。
这一定是因为一个线程正在覆盖锁内的结果对象,而另一个线程在锁外使用它们。
我猜我唯一的解决方案是向每个消费者返回一份结果副本,而不是原始结果,并且我不能允许在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);
}
}
我通常使用ConcurrentDictionary<TKey, Lazy<TValue>>
。这给你每把钥匙一把锁。它使得在获取时保持锁的策略变得可行。这也避免了缓存踩踏。它保证每个键只进行一次评估。Lazy<T>
完全自动锁定。
关于您的过期逻辑:您可以设置一个计时器,每X秒清理一次字典(或完全重写它)。