3

我编写了一个通用的 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);
    }
 }
4

1 回答 1

1

我通常使用一个ConcurrentDictionary<TKey, Lazy<TValue>>. 这为您提供了每把钥匙的锁。它使在获取时保持锁定的策略可行。这也避免了缓存标记。它保证每个密钥只会发生一次评估。Lazy<T>完全自动化锁定。

关于您的到期逻辑:您可以设置一个计时器,每 X 秒清理一次字典(或完全重写它)。

于 2013-07-11T15:15:29.447 回答