缓存类和已修改的集合

本文关键字:集合 修改 缓存 | 更新日期: 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秒清理一次字典(或完全重写它)。