Ich weiß, dass es unter bestimmten Umständen, z. B. bei Prozessen mit langer Laufzeit, wichtig ist, den ASP.NET-Cache zu sperren, um zu vermeiden, dass nachfolgende Anforderungen eines anderen Benutzers für diese Ressource den langen Prozess erneut ausführen, anstatt den Cache zu treffen.
Was ist der beste Weg in c #, um die Cache-Sperre in ASP.NET zu implementieren?
Der Vollständigkeit halber würde ein vollständiges Beispiel ungefähr so aussehen.
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;
quelle
globalObject
in einem Bereich zurück, in dem es tatsächlich nicht existiert. Was passieren sollte, ist,dataObject
dass es innerhalb der endgültigen Nullprüfung verwendet werden sollte und dass das Ereignis globalObject überhaupt nicht vorhanden sein muss.Es ist nicht erforderlich, die gesamte Cache-Instanz zu sperren, sondern nur den spezifischen Schlüssel zu sperren, für den Sie einfügen. Dh keine Notwendigkeit, den Zugang zur weiblichen Toilette zu blockieren, während Sie die männliche Toilette benutzen :)
Die folgende Implementierung ermöglicht das Sperren bestimmter Cache-Schlüssel unter Verwendung eines gleichzeitigen Wörterbuchs. Auf diese Weise können Sie GetOrAdd () für zwei verschiedene Schlüssel gleichzeitig ausführen - jedoch nicht für denselben Schlüssel zur gleichen Zeit.
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; } }
quelle
keyLocks.TryRemove(key, out locker)
<= wozu dient es?factory
Methode gleichzeitig ausführen . Nur wo T1 zuvor das ausgeführt hat,factory
das null zurückgegeben hat (damit der Wert nicht zwischengespeichert wird). Andernfalls erhalten T2 und T3 nur gleichzeitig den zwischengespeicherten Wert (was sicher sein sollte). Ich denke, die einfache Lösung ist das Löschen,keyLocks.TryRemove(key, out locker)
aber dann könnte das ConcurrentDictionary zu einem Speicherverlust werden, wenn eine große Anzahl verschiedener Schlüssel verwendet wird. Fügen Sie andernfalls eine Logik hinzu, um Sperren für einen Schlüssel vor dem Entfernen zu zählen, möglicherweise mithilfe eines Semaphors?Um zu wiederholen, was Pavel gesagt hat, glaube ich, dass dies die sicherste Art ist, es zu schreiben
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; }
quelle
Craig Shoemaker hat eine hervorragende Show beim Asp.net-Caching gemacht: http://polymorphicpodcast.com/shows/webperformance/
quelle
Ich habe mir die folgende Erweiterungsmethode ausgedacht:
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; }
Ich habe sowohl @ John Owen- als auch @ user378380-Antworten verwendet. Mit meiner Lösung können Sie auch int- und bool-Werte im Cache speichern.
Bitte korrigieren Sie mich, wenn es Fehler gibt oder wenn es etwas besser geschrieben werden kann.
quelle
Ich habe kürzlich ein Muster namens Correct State Bag Access Pattern gesehen, das dies zu berühren schien.
Ich habe es ein wenig modifiziert, um threadsicher zu sein.
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; }
quelle
myList
VariableInsert
Ausnahmen verhindern möchten,DAL.ListCustomers
ist keine Sperre erforderlich , nur wenn Sie sicherstellen möchten, dass diese einmal aufgerufen wird (wenn das Ergebnis jedoch null ist, wird es jedes Mal aufgerufen).In diesem Artikel von CodeGuru werden verschiedene Cache-Sperrszenarien sowie einige bewährte Methoden für die ASP.NET-Cache-Sperre erläutert:
Synchronisieren des Cache-Zugriffs in ASP.NET
quelle
Ich habe eine Bibliothek geschrieben, die dieses spezielle Problem löst: Rocks.Caching
Außerdem habe ich ausführlich über dieses Problem gebloggt und erklärt, warum es hier wichtig ist .
quelle
Ich habe den Code von @ user378380 für mehr Flexibilität geändert. Anstatt TResult zurückzugeben, wird jetzt ein Objekt zum Akzeptieren verschiedener Typen in der angegebenen Reihenfolge zurückgegeben. Fügen Sie auch einige Parameter für Flexibilität hinzu. Die ganze Idee gehört @ 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; }
quelle