2008-09-02 4 views
71

Я знаю, что в определенных обстоятельствах, таких как длительные процессы, важно блокировать кеш ASP.NET, чтобы избежать последующих запросов другого пользователя, чтобы этот ресурс снова выполнял длительный процесс, а не ударил кеш.Каков наилучший способ блокировки кеша в asp.net?

Каков наилучший способ в C# реализовать блокировку кеша в ASP.NET?

ответ

102

Вот основная картина:

  • Проверьте кэш для VALU е, вернуться, если его доступно
  • Если значение не находится в кэше, а затем осуществить блокировку
  • Внутри замка, проверьте кэш снова, вы, возможно, были заблокированы
  • Выполните значение просмотра и кэшировать
  • Освободить замок

В коде это выглядит следующим образом:

private static object ThisLock = new object(); 

public string GetFoo() 
{ 

    // try to pull from cache here 

    lock (ThisLock) 
    { 
    // cache was empty before we got the lock, check again inside the lock 

    // cache is still empty, so retreive the value here 

    // store the value in the cache here 
    } 

    // return the cached value here 

} 
+4

Если первая загрузка кэша занимает несколько минут, есть еще способ доступа к записи уже загружены? Скажем, если у меня есть GetFoo_AmazonArticlesByCategory (строка categoryKey). Наверное, это будет что-то вроде блокировки для категорииKey. – 2009-06-23 17:54:32

+4

Называется «двойная проверка». http://en.wikipedia.org/wiki/Double-checked_locking – 2013-01-24 18:13:40

0

Эта статья из CodeGuru объясняет различные кэш блокировки сценариев, а также некоторые рекомендации для блокировки кэша ASP.NET:

Synchronizing Cache Access in ASP.NET

1

Я видел один шаблон недавно под названием Правильный S Tate Bag Access Pattern, который, казалось, касался этого.

Я немного изменил его, чтобы быть потокобезопасным.

http://weblogs.asp.net/craigshoemaker/archive/2008/08/28/asp-net-caching-and-performance.aspx

private static object _listLock = new object(); 

public List List() { 
    string cacheKey = "customers"; 
    List myList = Cache[cacheKey] as List; 
    if(myList == null) { 
     lock (_listLock) { 
      myList = Cache[cacheKey] as List; 
      if (myList == null) { 
       myList = DAL.ListCustomers(); 
       Cache.Insert(cacheKey, mList, null, SiteConfig.CacheDuration, TimeSpan.Zero); 
      } 
     } 
    } 
    return myList; 
} 
+0

Не могли ли два потока получить истинный результат для (myList == null)? Затем оба потока обращаются к DAL.ListCustomers() и вставляют результаты в кеш. – frankadelic 2010-01-13 01:24:15

+0

Добавлен поток безопасности. – 2010-01-13 08:43:44

+4

После блокировки вам нужно снова проверить кеш, а не локальную переменную `myList` – orip 2010-09-08 08:20:41

28

Для полноты полный пример будет выглядеть примерно так.

private static object ThisLock = new object(); 
... 
object dataObject = Cache["globalData"]; 
if(dataObject == null) 
{ 
    lock(ThisLock) 
    { 
     dataObject = Cache["globalData"]; 

     if(dataObject == null) 
     { 
      //Get Data from db 
      dataObject = GlobalObj.GetData(); 
      Cache["globalData"] = dataObject; 
     } 
    } 
} 
return dataObject; 
14

Просто повторить то, что сказал Павел, я считаю, что это самый поточно способ написания его

private T GetOrAddToCache<T>(string cacheKey, GenericObjectParamsDelegate<T> creator, params object[] creatorArgs) where T : class, new() 
    { 
     T returnValue = HttpContext.Current.Cache[cacheKey] as T; 
     if (returnValue == null) 
     { 
      lock (this) 
      { 
       returnValue = HttpContext.Current.Cache[cacheKey] as T; 
       if (returnValue == null) 
       { 
        returnValue = creator(creatorArgs); 
        if (returnValue == null) 
        { 
         throw new Exception("Attempt to cache a null reference"); 
        } 
        HttpContext.Current.Cache.Add(
         cacheKey, 
         returnValue, 
         null, 
         System.Web.Caching.Cache.NoAbsoluteExpiration, 
         System.Web.Caching.Cache.NoSlidingExpiration, 
         CacheItemPriority.Normal, 
         null); 
       } 
      } 
     } 

     return returnValue; 
    } 
2

Я придумал следующий метод расширения:

private static readonly object _lock = new object(); 

public static TResult GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action, int duration = 300) { 
    TResult result; 
    var data = cache[key]; // Can't cast using as operator as TResult may be an int or bool 

    if (data == null) { 
     lock (_lock) { 
      data = cache[key]; 

      if (data == null) { 
       result = action(); 

       if (result == null) 
        return result; 

       if (duration > 0) 
        cache.Insert(key, result, null, DateTime.UtcNow.AddSeconds(duration), TimeSpan.Zero); 
      } else 
       result = (TResult)data; 
     } 
    } else 
     result = (TResult)data; 

    return result; 
} 

Я использовал оба @John Owen и @ user378380 ответы. Мое решение позволяет также хранить значения int и bool в кеше.

Пожалуйста, исправьте меня, если есть какие-либо ошибки или можно ли написать немного лучше.

0

Я написал библиотеку, которая решает этот конкретный вопрос: Rocks.Caching

Кроме того, я уже писал об этой проблеме в деталях и объяснил, почему это важно here.

0

Я модифицировал код @ user378380 для большей гибкости.Вместо возврата TResult теперь возвращает объект для принятия разных типов в порядке. Также добавление некоторых параметров для гибкости. Вся идея принадлежит @ user378380.

private static readonly object _lock = new object(); 


//If getOnly is true, only get existing cache value, not updating it. If cache value is null then  set it first as running action method. So could return old value or action result value. 
//If getOnly is false, update the old value with action result. If cache value is null then  set it first as running action method. So always return action result value. 
//With oldValueReturned boolean we can cast returning object(if it is not null) appropriate type on main code. 


public static object GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action, 
    DateTime absoluteExpireTime, TimeSpan slidingExpireTime, bool getOnly, out bool oldValueReturned) 
{ 
    object result; 
    var data = cache[key]; 

    if (data == null) 
    { 
     lock (_lock) 
     { 
      data = cache[key]; 

      if (data == null) 
      { 
       oldValueReturned = false; 
       result = action(); 

       if (result == null) 
       {      
        return result; 
       } 

       cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime); 
      } 
      else 
      { 
       if (getOnly) 
       { 
        oldValueReturned = true; 
        result = data; 
       } 
       else 
       { 
        oldValueReturned = false; 
        result = action(); 
        if (result == null) 
        {        
         return result; 
        } 

        cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime); 
       } 
      } 
     } 
    } 
    else 
    { 
     if(getOnly) 
     { 
      oldValueReturned = true; 
      result = data; 
     } 
     else 
     { 
      oldValueReturned = false; 
      result = action(); 
      if (result == null) 
      { 
       return result; 
      } 

      cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime); 
     }    
    } 

    return result; 
} 
2

Нет необходимости блокировать весь экземпляр кеша, вместо этого нам нужно только заблокировать конкретный ключ, который вы вставляете. I.e. Нет необходимости блокировать доступ к туалету для женщин, когда вы используете мужской туалет :)

Нижеприведенная реализация позволяет блокировать определенные ключи кеша, используя параллельный словарь. Таким образом, вы можете запускать GetOrAdd() для двух разных ключей одновременно - но не для одного и того же ключа одновременно.

using System; 
using System.Collections.Concurrent; 
using System.Web; 
using System.Web.Caching; 

public static class CacheExtensions 
{ 
    private static ConcurrentDictionary<string, object> keyLocks = new ConcurrentDictionary<string, object>(); 

    /// <summary> 
    /// Get or Add the item to the cache 
    /// </summary> 
    public static T GetOrAdd<T>(this Cache cache, string key, Func<T> factory, int durationInSeconds, bool synchronised = true) 
     where T : class 
    { 
     // Try and get value from the cache 
     var value = cache.Get(key); 
     if (value == null) 
     { 
      // If not yet cached, lock the key value and add to cache 
      lock (keyLocks.GetOrAdd(key, new object())) 
      { 
       // Try and get from cache again in case it has been added in the meantime 
       value = HttpRuntime.Cache.Get(key); 
       if (value == null && (value = factory()) != null) 
       { 
        HttpRuntime.Cache.Insert(key, 
         value, 
         null, 
         DateTime.Now.AddSeconds(durationInSeconds), 
         Cache.NoSlidingExpiration, 
         CacheItemPriority.Default, 
         null); 
       } 

       // Remove temporary key lock 
       object locker; 
       keyLocks.TryRemove(key, out locker); 
      } 
     } 

     return value as T; 
    } 
} 
Смежные вопросы