我知道在某些情况下,例如长时间运行的进程,锁定ASP.NET缓存非常重要,以避免其他用户对该资源的后续请求再次执行长进程而不是命中缓存.
在ASP.NET中实现缓存锁定的最佳方法是什么?
这是基本模式:
检查缓存中的值,如果可用则返回
如果该值不在缓存中,则实现锁定
在锁内,再次检查缓存,您可能已被阻止
执行值查找并缓存它
解锁
在代码中,它看起来像这样:
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 }
为了完整性,一个完整的例子看起来像这样.
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;
无需锁定整个缓存实例,而只需要锁定要插入的特定密钥.也就是说,当你使用男厕时,不需要阻止进入女厕所:)
下面的实现允许使用并发字典锁定特定的高速缓存密钥.这样,您可以同时为两个不同的键运行GetOrAdd() - 但不能同时为同一个键运行.
using System; using System.Collections.Concurrent; using System.Web.Caching; public static class CacheExtensions { private static ConcurrentDictionarykeyLocks = new ConcurrentDictionary (); /// /// Get or Add the item to the cache using the given key. Lazily executes the value factory only if/when needed /// public static T GetOrAdd(this Cache cache, string key, int durationInSeconds, Func 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; } }
为了回应Pavel所说的,我相信这是编写它的最安全的方式
private T GetOrAddToCache(string cacheKey, GenericObjectParamsDelegate 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; }