Ik weet dat het in bepaalde omstandigheden, zoals bij langlopende processen, belangrijk is om de ASP.NET-cache te vergrendelen om te voorkomen dat latere verzoeken van een andere gebruiker voor die bron het lange proces opnieuw uitvoeren in plaats van de cache te gebruiken.
p>
Wat is de beste manier in c# om cachevergrendeling in ASP.NET te implementeren?
Antwoord 1, autoriteit 100%
Dit is het basispatroon:
- Controleer de cache op de waarde, keer terug als deze beschikbaar is
- Als de waarde niet in de cache staat, implementeer dan een vergrendeling
- Controleer de cache opnieuw in het slot, je bent mogelijk geblokkeerd
- Voer de waarde opzoeken en cache deze in
- Maak het slot los
In code ziet het er als volgt uit:
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
}
Antwoord 2, autoriteit 27%
Voor de volledigheid zou een volledig voorbeeld er ongeveer zo uitzien.
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;
Antwoord 3, autoriteit 20%
Het is niet nodig om de hele cache-instantie te vergrendelen, maar we hoeven alleen de specifieke sleutel te vergrendelen waarvoor u deze invoegt.
D.w.z. U hoeft de toegang tot het damestoilet niet te blokkeren terwijl u het herentoilet gebruikt 🙂
Met de onderstaande implementatie kunnen specifieke cachesleutels worden vergrendeld met behulp van een gelijktijdige woordenboek. Op deze manier kunt u GetOrAdd() voor twee verschillende sleutels tegelijkertijd uitvoeren – maar niet voor dezelfde sleutel tegelijkertijd.
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;
}
}
Antwoord 4, autoriteit 11%
Om te herhalen wat Pavel zei, ik geloof dat dit de meest threadveilige manier is om het te schrijven
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;
}
Antwoord 5, autoriteit 2%
Craig Shoemaker heeft een uitstekende show gemaakt op asp.net-caching:
http://polymorphicpodcast.com/shows/webperformance/
Antwoord 6, autoriteit 2%
Ik heb de volgende uitbreidingsmethode bedacht:
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;
}
Ik heb zowel antwoorden van @John Owen als @user378380 gebruikt. Met mijn oplossing kun je ook int- en bool-waarden in de cache opslaan.
Corrigeer me als er fouten zijn of als het iets beter geschreven kan worden.
Antwoord 7
Ik zag onlangs een patroon met de naam Correct State Bag Access Pattern, dat hierop leek aan te raken.
Ik heb het een beetje aangepast om thread-safe te zijn.
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;
}
Antwoord 8
Dit artikel van CodeGuru legt verschillende scenario’s voor cachevergrendeling uit, evenals enkele best practices voor ASP.NET-cachevergrendeling:
Cachetoegang synchroniseren in ASP.NET
Antwoord 9
Ik heb een bibliotheek geschreven die dat specifieke probleem oplost: Rocks.Caching
Ik heb ook over dit probleem in details geblogd en uitgelegd waarom het belangrijk is hier .