¿Cuál es la mejor manera de bloquear el caché en asp.net?

Sé que en determinadas circunstancias, como los procesos de larga ejecución, es importante bloquear la memoria caché de ASP.NET para evitar solicitudes posteriores de otro usuario para que ese recurso vuelva a ejecutar el proceso largo en lugar de presionar la caché.

¿Cuál es la mejor manera en c # para implementar el locking de caché en ASP.NET?

Aquí está el patrón básico:

  • Verifique el valor en la caché, devuelva si está disponible
  • Si el valor no está en la memoria caché, implemente un locking
  • Dentro de la cerradura, revisa la caché de nuevo, es posible que hayas sido bloqueado
  • Realice la búsqueda de valor y almacénelo en caché
  • Liberar el locking

En el código, se ve así:

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 } 

Para completar, un ejemplo completo se vería así.

 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; 

Solo para repetir lo que dijo Pavel, creo que esta es la forma más segura de escribirlo

 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; } 

No es necesario bloquear toda la instancia de la memoria caché, sino que solo debemos bloquear la clave específica para la que está insertando. Es decir, no es necesario bloquear el acceso al baño femenino mientras usa el baño masculino 🙂

La implementación a continuación permite el locking de claves de caché específicas utilizando un diccionario simultáneo. De esta forma puede ejecutar GetOrAdd () para dos claves diferentes al mismo tiempo, pero no para la misma clave al mismo tiempo.

 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, 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; } } 

Craig Shoemaker ha realizado un excelente show en el almacenamiento en caché de asp.net: http://polymorphicpodcast.com/shows/webperformance/

He encontrado el siguiente método de extensión:

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

He usado las respuestas de @John Owen y @ user378380. Mi solución también te permite almacenar valores int y bool dentro de la memoria caché.

Por favor corrígeme si hay algún error o si puede escribirse un poco mejor.

Vi un patrón llamado recientemente Patrón correcto de acceso a bolsas de estado, que parecía tocar esto.

Lo modifiqué un poco para que sea seguro para subprocesos.

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; } 

Este artículo de CodeGuru explica varios escenarios de locking de caché, así como algunas mejores prácticas para el locking de caché ASP.NET:

Sincronización del acceso a caché en ASP.NET

He escrito una biblioteca que resuelve ese problema en particular: Rocks.Caching

También he escrito un blog sobre este problema en detalles y he explicado por qué es importante aquí .

Modifiqué el código de @ user378380 para más flexibilidad. En lugar de devolver, TResult ahora devuelve un objeto para aceptar diferentes tipos en orden. También agregando algunos parámetros para la flexibilidad. Toda la idea pertenece a @ 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, string 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; }