80

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

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

4

10 回答 10

116

这是基本模式:

  • 检查缓存中的值,如果可用则返回
  • 如果值不在缓存中,则执行锁
  • 在锁里面,再检查一下缓存,你可能已经被阻塞了
  • 执行值查找并缓存它
  • 释放锁

在代码中,它看起来像这样:

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

}
于 2008-09-02T17:12:26.023 回答
31

为了完整起见,一个完整的例子看起来像这样。

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;
于 2008-09-03T08:17:22.900 回答
23

无需锁定整个缓存实例,我们只需要锁定您要插入的特定键。即当您使用男厕所时,无需阻止进入女厕所 :)

下面的实现允许使用并发字典锁定特定的缓存键。这样,您可以同时为两个不同的键运行 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;
    }
}
于 2016-08-12T00:14:05.080 回答
13

只是为了呼应 Pavel 所说的,我相信这是编写它的最线程安全的方式

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;
    }
于 2010-06-28T19:23:38.807 回答
2

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

于 2008-09-02T10:00:37.327 回答
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 值。

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

于 2014-04-18T12:56:04.537 回答
1

我最近看到了一种称为正确状态包访问模式的模式,它似乎涉及到这一点。

我对其进行了一些修改以使其成为线程安全的。

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;
}
于 2008-09-02T17:29:56.747 回答
0

CodeGuru 的这篇文章解释了各种缓存锁定方案以及 ASP.NET 缓存锁定的一些最佳实践:

在 ASP.NET 中同步缓存访问

于 2008-09-02T10:02:17.040 回答
0

我写了一个库来解决这个特定问题:Rocks.Caching

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

于 2014-06-06T11:16:23.520 回答
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;
}
于 2014-10-23T07:28:57.990 回答