2013-07-11 2 views
3

Я написал общий класс Cache, предназначенный для возврата объекта в памяти, и иногда оценивает src (IQueryable или функцию, возвращающую IQueryable). Его использование в нескольких местах в моем приложении, где получение большого количества данных через сущность framework является дорогостоящим.Класс кэша и измененные коллекции

Это называется

//class level 
    private static CachedData<Foo> fooCache= new CachedData<Foo>(); 

    //method level 
    var results = fooCache.GetData("foos", fooRepo.Include("Bars")); 

Хотя, казалось нормально работать в тестировании, работает на занятом веб-сервера я вижу некоторые проблемы с «Коллекция была изменена, операция перечисления не может выполнить.» ошибки в коде, который потребляет результаты.

Это должно быть потому, что один поток переписывает объект результатов внутри замка, а другой - за пределы замка.

Я предполагаю, что мое единственное решение состоит в том, чтобы вернуть копию результатов каждому потребителю, а не оригиналу, и что я не могу разрешить копирование во время блокировки 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 > для DataCache и возвращать результаты как .ToList(). AsReadOnly() получить меня более безопасным? Если нет, можете ли вы указать мне немного более решительно к решению. – Andiih

+0

Перед тем, как поместить элемент в кеш, вы должны выполнить .ToList(). AsReadOnly(). Это сделает вас безопасным. Этот код должен выполняться как часть обратного вызова 'Lazy ', – usr

+0

Это то, что вы предлагаете? http://reedcopsey.com/2011/01/16/concurrentdictionarytkeytvalue-used-with-lazyt/ – Andiih

Смежные вопросы