2013-07-11 56 views
3

我已經寫了一個通用的緩存類,用於返回內存中的對象,並且僅偶爾評估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); 
    } 
} 
+0

您的緩存完全不是線程安全的,不應使用。 – SLaks

+0

是@SLaks我知道:-(! – Andiih

回答

1

我通常使用一個ConcurrentDictionary<TKey, Lazy<TValue>>。這給你每個鍵一個鎖。它使策略在取得可行時保持鎖定。這也避免了緩存衝突。它保證每個密鑰只有一次評估會發生。 Lazy<T>完全自動完成鎖定。

關於你的過期邏輯:你可以設置一個計時器,每隔X秒清理一次字典(或者完全重寫)。

+0

將使用ConcurrentDictionary <字符串,IEnumerable的>爲數據高速緩存並返回結果.ToList()。AsReadOnly()讓我更加安全嗎?如果沒有,你能指出我有點 – Andiih

+0

在將項目放入緩存之前,您應該先執行.ToList()。AsReadOnly()。這會讓您安全起見。此代碼應該作爲'Lazy '回調的一部分運行,以便它自動線程安全 – usr

+0

這就是你的建議?http://reedcopsey.com/2011/01/16/concurrentdictionarytkeytvalue-used-with-lazyt/ – Andiih