C# 缓存类和修改的集合

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

我编写了一个通用的缓存类,该类旨在返回内存中的对象,并且只偶尔计算src(IQueryable或返回IQueryable的函数)。它在我的应用程序中的几个地方使用,在这些地方,通过实体框架获取大量数据非常昂贵

它被称为

    //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上的其他地方也有建议。很好的模式。