代码之家  ›  专栏  ›  技术社区  ›  John Owen

在ASP.NET中锁定缓存的最佳方法是什么?

  •  71
  • John Owen  · 技术社区  · 18 年前

    我知道在某些情况下,例如长时间运行的进程,锁定ASP.NET缓存很重要,这样可以避免其他用户对该资源的后续请求再次执行长进程,而不是访问缓存。

    C中在ASP.NET中实现缓存锁定的最佳方法是什么?

    10 回复  |  直到 8 年前
        1
  •  104
  •   a7drew    18 年前

    基本模式如下:

    • 检查缓存中的值,如果该值可用,则返回
    • 如果该值不在缓存中,则实现一个锁
    • 在锁内,再次检查缓存,您可能已被阻止
    • 执行值查找和缓存
    • 释放锁

    在代码中,如下所示:

    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
    
    }
    
        2
  •  28
  •   PHeiberg    14 年前

    为了完整性起见,完整的示例如下所示。

    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;
    
        3
  •  13
  •   user378380    16 年前

    正如帕维尔所说,我相信这是最安全的写作方式。

    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;
        }
    
        4
  •  6
  •   cwills    8 年前

    不需要锁定整个缓存实例,我们只需要锁定您要插入的特定密钥。 也就是说,当你使用男厕所时,不需要阻塞进入女厕所的通道:)

    下面的实现允许使用并发字典锁定特定的缓存键。这样,您可以同时为两个不同的键运行getoradd(),但不能同时为同一个键运行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;
        }
    }
    
        5
  •  2
  •   khebbie    18 年前

    Craig Shoemaker在ASP.NET缓存方面做了出色的展示: http://polymorphicpodcast.com/shows/webperformance/

        6
  •  2
  •   nfplee    12 年前

    我提出了以下扩展方法:

    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值。

    如果有任何错误或是否可以写得更好,请纠正我。

        7
  •  1
  •   Seb Nilsson    14 年前

    我最近看到了一个模式,叫做正确的状态包访问模式,它似乎涉及到这个问题。

    我做了一些修改以保证线程安全。

    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;
    }
    
        8
  •  0
  •   Jon Limjap    18 年前

    来自Codeguru的这篇文章解释了各种缓存锁定场景以及一些ASP.NET缓存锁定的最佳实践:

    Synchronizing Cache Access in ASP.NET

        9
  •  0
  •   Michael Logutov    12 年前

    我写了一个图书馆来解决这个问题: Rocks.Caching

    另外,我在博客中详细介绍了这个问题,并解释了为什么它很重要 here .

        10
  •  0
  •   Tarık Özgün Güner    11 年前

    为了提高灵活性,我修改了@user378380的代码。现在返回的不是treult而是按顺序接受不同类型的对象。还添加了一些灵活的参数。所有的想法都属于 @ USER 78380。

     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;
    }
    
    推荐文章