Каков наилучший способ заблокировать кэш в asp.net? - PullRequest
71 голосов
/ 02 сентября 2008

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

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

Ответы [ 10 ]

104 голосов
/ 02 сентября 2008

Вот базовый шаблон:

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

В коде это выглядит так:

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

}
28 голосов
/ 03 сентября 2008

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

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;
13 голосов
/ 28 июня 2010

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

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;
    }
12 голосов
/ 12 августа 2016

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

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

using System;
using System.Collections.Concurrent;
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 using the given key. Lazily executes the value factory only if/when needed
    /// </summary>
    public static T GetOrAdd<T>(this Cache cache, string key, int durationInSeconds, Func<T> factory)
        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 = cache.Get(key);
                if (value == null && (value = factory()) != null)
                {
                    // TODO: Some of these parameters could be added to method signature later if required
                    cache.Insert(
                        key: key,
                        value: value,
                        dependencies: null,
                        absoluteExpiration: DateTime.Now.AddSeconds(durationInSeconds),
                        slidingExpiration: Cache.NoSlidingExpiration,
                        priority: CacheItemPriority.Default,
                        onRemoveCallback: null);
                }

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

        return value as T;
    }
}
2 голосов
/ 18 апреля 2014

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

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 в кеше.

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

2 голосов
/ 02 сентября 2008

Крейг Шумейкер провел отличное шоу на кешировании asp.net: http://polymorphicpodcast.com/shows/webperformance/

1 голос
/ 02 сентября 2008

Я видел один шаблон, недавно названный «Правильный шаблон доступа к сумке состояния», который, казалось, касался этого.

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

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 голосов
/ 23 октября 2014

Я изменил код @ 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;
}
0 голосов
/ 06 июня 2014

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

Также я подробно рассказал об этой проблеме и объяснил, почему она важна здесь .

0 голосов
/ 02 сентября 2008

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

Синхронизация доступа к кэшу в ASP.NET

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...