【问题标题】:Cache class and modified collections缓存类和修改的集合
【发布时间】:2013-07-11 15:12:48
【问题描述】:

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

【问题讨论】:

  • 您的缓存完全是非线程安全的,不应使用。
  • 是的@SLaks 我知道 :-( !

标签: c# multithreading caching collections


【解决方案1】:

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

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

【讨论】:

  • 会为 DataCache 使用 ConcurrentDictionary> 并将结果返回为 .ToList().AsReadOnly() 让我更安全吗?如果没有,您能否更坚定地指出我的解决方案。
  • 您应该在将项目放入缓存之前执行 .ToList().AsReadOnly() 。那会让你安全。此代码应作为Lazy&lt;T&gt; 回调的一部分运行,以便自动成为线程安全的。
  • 这就是你的建议? reedcopsey.com/2011/01/16/…
  • 是的。网上其他地方也有推荐。很棒的图案。
猜你喜欢
  • 2012-04-27
  • 1970-01-01
  • 2021-11-18
  • 1970-01-01
  • 2013-03-17
  • 2012-02-17
  • 1970-01-01
  • 1970-01-01
  • 2011-07-22
相关资源
最近更新 更多