C# 缓存类和修改的集合
我编写了一个通用的缓存类,该类旨在返回内存中的对象,并且只偶尔计算src(IQueryable或返回IQueryable的函数)。它在我的应用程序中的几个地方使用,在这些地方,通过实体框架获取大量数据非常昂贵 它被称为C# 缓存类和修改的集合,c#,multithreading,caching,collections,C#,Multithreading,Caching,Collections,我编写了一个通用的缓存类,该类旨在返回内存中的对象,并且只偶尔计算src(IQueryable或返回IQueryable的函数)。它在我的应用程序中的几个地方使用,在这些地方,通过实体框架获取大量数据非常昂贵 它被称为 //class level private static CachedData<Foo> fooCache= new CachedData<Foo>(); //method level var results = foo
//class level
private static CachedData<Foo> fooCache= new CachedData<Foo>();
//method level
var results = fooCache.GetData("foos", fooRepo.Include("Bars"));
//类级别
private static CachedData fooCache=new CachedData();
//方法级
var results=fooCache.GetData(“foos”,fooRepo.Include(“bar”);
虽然它在测试中似乎工作正常,但在繁忙的web服务器上运行时,我发现“集合已修改;枚举操作可能无法执行”存在一些问题。代码中的错误会消耗结果
这一定是因为一个线程在锁内覆盖results对象,而另一个线程在锁外使用它们
我猜我唯一的解决方案是将结果的副本返回给每个使用者,而不是原始使用者,并且我不能允许副本在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);
}
}
公共类缓存数据,其中T:class
{
专用静态字典数据缓存{get;set;}
公共静态字典过期{get;set;}
公共int TTL{get;set;}
私有对象lo=新对象();
公共缓存数据()
{
TTL=600;
Expire=新字典();
DataCache=newdictionary();
}
公共IEnumerable GetData(字符串键,Func src)
{
var bc=品牌密钥(密钥);
如果(!DataCache.ContainsKey(bc))获取(bc,src);
if(DateTime.Now>Expire[bc])获取(bc,src);
返回数据缓存[bc];
}
公共IEnumerable GetData(字符串键,IQueryable src)
{
var bc=品牌密钥(密钥);
如果(!DataCache.ContainsKey(bc))| |(DateTime.Now>Expire[bc])获取(bc,src);
返回数据缓存[bc];
}
私有void获取(字符串键,IQueryable src)
{
锁(低)
{
如果((!DataCache.ContainsKey(key))| |(DateTime.Now>Expire[key]))ExecuteFetch(key,src);
}
}
私有无效获取(字符串键,Func src)
{
锁(低)
{
如果((!DataCache.ContainsKey(key))| |(DateTime.Now>Expire[key]))ExecuteFetch(key,src());
}
}
私有void ExecuteFetch(字符串键,IQueryable src)
{
如果(!DataCache.ContainsKey(key))DataCache.Add(key,src.ToList());
else DataCache[key]=src.ToList();
如果(!Expire.ContainsKey(key))Expire.Add(key,DateTime.Now.AddSeconds(TTL));
else Expire[key]=DateTime.Now.AddSeconds(TTL);
}
私有字符串brandKey(字符串密钥,int?brandid=null)
{
返回string.Format(“{0}/{1}”,brandid??Config.brandid,key);
}
}
我通常使用ConcurrentDictionary
。每把钥匙都有一把锁。这使得在获取时保持锁的策略可行。这也避免了缓存踩踏。它保证每个密钥只进行一次评估<代码>惰性完全自动锁定
关于过期逻辑:您可以设置一个计时器,每X秒清理一次字典(或完全重写字典)。您的缓存是完全非线程安全的,不应使用。Yes@SLaks I know:-(!是否将ConcurrentDictionary用于数据缓存,并将结果返回为.ToList().AsReadOnly())让我更安全?如果不是,你能更坚定地告诉我一个解决方案吗?你应该执行.ToList().AsReadOnly()命令在将项目放入缓存之前。这将使您更加安全。此代码应作为
惰性
回调的一部分运行,以便自动实现线程安全。这就是您的建议?是的,正是这样。在web上的其他地方也有建议。很好的模式。