Wat is de beste manier om de cache in asp.net te vergrendelen?

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 .

Other episodes