Quelle est la meilleure façon de verrouiller le cache dans asp.net?

Je sais que dans certaines circonstances, telles que les processus de longue durée, il est important de verrouiller le cache ASP.NET pour éviter que des demandes ultérieures d’un autre utilisateur pour que cette ressource exécute à nouveau le processus long au lieu de bash le cache.

Quelle est la meilleure façon d’utiliser c # pour implémenter le locking du cache dans ASP.NET?

Voici le modèle de base:

  • Vérifiez le cache pour la valeur, retournez si elle est disponible
  • Si la valeur n’est pas dans le cache, implémentez un verrou
  • Dans le verrou, vérifiez à nouveau le cache, vous avez peut-être été bloqué
  • Effectuer la recherche de valeur et la mettre en cache
  • Relâchez le verrou

Dans le code, cela ressemble à ceci:

private static object ThisLock = new object(); public ssortingng 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 } 

Pour être complet, un exemple complet ressemblerait à ceci.

 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; 

Juste pour faire écho à ce que Pavel a dit, je crois que c’est la manière la plus sûre d’écrire

 private T GetOrAddToCache(ssortingng 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; } 

Il n’est pas nécessaire de verrouiller l’intégralité de l’instance du cache, il suffit plutôt de verrouiller la clé spécifique que vous insérez. Ie pas besoin de bloquer l’access aux canvasttes des femmes pendant que vous utilisez les canvasttes pour hommes 🙂

L’implémentation ci-dessous permet de verrouiller des clés de cache spécifiques à l’aide d’un dictionnaire simultané. De cette façon, vous pouvez exécuter GetOrAdd () pour deux clés différentes en même temps, mais pas pour la même clé en même temps.

 using System; using System.Collections.Concurrent; using System.Web.Caching; public static class CacheExtensions { private static ConcurrentDictionary keyLocks = 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, ssortingng 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; } } 

Craig Shoemaker a fait un excellent spectacle sur la mise en cache asp.net: http://polymorphicpodcast.com/shows/webperformance/

Je suis venu avec la méthode d’extension suivante:

 private static readonly object _lock = new object(); public static TResult GetOrAdd(this Cache cache, ssortingng key, Func 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; } 

J’ai utilisé les deux réponses @John Owen et @ user378380. Ma solution vous permet de stocker des valeurs int et bool dans le cache.

S’il vous plaît corrigez-moi s’il y a des erreurs ou si cela peut être écrit un peu mieux.

J’ai vu récemment un modèle appelé Correct State Access Access Pattern, qui semblait toucher à cela.

Je l’ai un peu modifié pour être thread-safe.

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

 private static object _listLock = new object(); public List List() { ssortingng 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; } 

Cet article de CodeGuru explique divers scénarios de locking du cache, ainsi que certaines pratiques recommandées pour le locking du cache ASP.NET:

Synchronisation de l’access au cache dans ASP.NET

J’ai écrit une bibliothèque qui résout ce problème particulier: Rocks.Caching

J’ai aussi parlé de ce problème en détail et expliqué pourquoi c’est important ici .

J’ai modifié le code de @ user378380 pour plus de flexibilité. Au lieu de retourner TResult, il retourne maintenant un object pour accepter différents types dans l’ordre. Ajout de certains parameters pour la flexibilité. Toute l’idée appartient à @ 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(this Cache cache, ssortingng key, Func 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; }