Asp.net में कैश लॉक करने का सबसे अच्छा तरीका क्या है?


80

मैं कुछ परिस्थितियों में जानता हूं, जैसे कि लंबे समय से चल रही प्रक्रियाएं, एएसपी.नेट कैश को लॉक करना महत्वपूर्ण है ताकि कैश को हिट करने के बजाय फिर से लंबी प्रक्रिया को निष्पादित करने से उस संसाधन के लिए किसी अन्य उपयोगकर्ता द्वारा अनुरोधों से बचा जा सके।

ASP.NET में कैश लॉकिंग को लागू करने का c # में सबसे अच्छा तरीका क्या है?

जवाबों:


114

यहाँ मूल पैटर्न है:

  • मूल्य के लिए कैश की जांच करें, यदि उपलब्ध हो तो वापस करें
  • यदि मूल्य कैश में नहीं है, तो लॉक लागू करें
  • लॉक के अंदर, कैश को फिर से जांचें, आप अवरुद्ध हो गए होंगे
  • मान प्रदर्शन ऊपर देखो और इसे कैश
  • ताला जारी करो

कोड में, यह इस तरह दिखता है:

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

}

4
यदि कैश का पहला लोड कुछ मिनट लेता है, तो क्या अभी भी पहले से लोड की गई प्रविष्टियों तक पहुंचने का एक तरीका है? अगर मुझे GetFoo_AmazonArticlesByCategory (स्ट्रिंग केकेजी) मिलता है तो बताएं। मुझे लगता है कि यह प्रति श्रेणी लॉक की तरह कुछ करके होगा।
मैथियस एफ

5
जिसे "डबल-चेकिंग लॉकिंग" कहा जाता है। en.wikipedia.org/wiki/Double-checked_locking
ब्रैड गिगैन

32

पूर्णता के लिए एक पूर्ण उदाहरण कुछ इस तरह दिखाई देगा।

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;

7
if (dataObject == null) {लॉक (ThisLock) {if (dataOject == null) // बेशक यह अभी भी अशक्त है!
कांस्टेंटिन

30
@ कॉन्स्टेंटिन: वास्तव में, किसी ने कैश को अपडेट नहीं किया होगा जब आप लॉक हासिल करने के लिए इंतजार कर रहे थे ()
ट्यूडर ओलियारू

12
@ जॉन ओवेन - लॉक स्टेटमेंट के बाद आपको फिर से कैश से ऑब्जेक्ट प्राप्त करने का प्रयास करना होगा!
पावेल निकोलोव

3
-1, कोड गलत है (अन्य टिप्पणियों को पढ़ें), आप इसे ठीक क्यों नहीं करते हैं? लोग आपके उदाहरण का उपयोग करने का प्रयास कर सकते हैं।
10:२०

11
यह कोड वास्तव में अभी भी गलत है। आप globalObjectउस दायरे में लौट रहे हैं, जहां यह वास्तव में मौजूद नहीं है। क्या होना चाहिए कि dataObjectअंतिम अशक्त जांच के अंदर उपयोग किया जाना चाहिए, और GlobalObject घटना की आवश्यकता नहीं है सब पर मौजूद है।
स्कॉट एंडरसन

22

पूरे कैश उदाहरण को लॉक करने की कोई आवश्यकता नहीं है, बल्कि हमें केवल उस विशिष्ट कुंजी को लॉक करने की आवश्यकता है जिसे आप डाल रहे हैं। पुरुष शौचालय का उपयोग करते समय महिला शौचालय की पहुंच को ब्लॉक करने की कोई आवश्यकता नहीं है :)

नीचे दिया गया कार्यान्वयन एक समवर्ती शब्दकोश का उपयोग करके विशिष्ट कैश-कीज़ को लॉक करने की अनुमति देता है। इस तरह से आप GetOrAdd () एक ही समय में दो अलग-अलग कुंजी के लिए चला सकते हैं - लेकिन एक ही समय में एक ही कुंजी के लिए नहीं।

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

keyLocks.TryRemove(key, out locker) <= इसका क्या उपयोग है?
iMatoria

2
यह भी खूब रही। कैश को लॉक करने का पूरा बिंदु उस विशिष्ट कुंजी के लिए मान प्राप्त करने के लिए किए गए कार्य की नकल करने से बचना है। पूरे कैश को या यहां तक ​​कि वर्गों को वर्ग द्वारा लॉक करना मूर्खतापूर्ण है। आप बिल्कुल यही चाहते हैं - एक ताला जो कहता है कि "मुझे <कुंजी> के लिए मूल्य मिल रहा है" बाकी सभी को बस मेरा इंतजार है। " विस्तार विधि भी चालाक है। एक में दो महान विचार! यह वह उत्तर होना चाहिए जो लोग पाते हैं। धन्यवाद।
डैनो

1
@ आईमैटोरिया, एक बार उस कुंजी के लिए कैश में कुछ होता है, तो उस लॉक ऑब्जेक्ट को चारों ओर रखने या कुंजियों के शब्दकोश में प्रविष्टि का कोई मतलब नहीं है - यह एक हटाने की कोशिश है क्योंकि लॉक को पहले ही किसी अन्य द्वारा शब्दकोश से हटा दिया गया हो सकता है। थ्रेड जो पहले आया था - उस कुंजी पर प्रतीक्षा करने वाले सभी थ्रेड्स अब उस कोड सेक्शन में हैं, जहां वे अभी कैश से मूल्य प्राप्त करते हैं, लेकिन हटाने के लिए लॉक नहीं है।
डैनो

मुझे स्वीकृत उत्तर की तुलना में यह दृष्टिकोण बहुत अच्छा लगता है। हालांकि, छोटे नोट: आप पहले cache.Key का उपयोग करते हैं, फिर आप पर फ़्यूचर HttpRuntime.Cache.Get का उपयोग करते हैं।
स्टाटकटा

@MindaugasTvaronavicius अच्छा पकड़, आप सही हैं, इसका एक किनारा मामला जहां टी 2 और टी 3 factoryसमवर्ती तरीके से निष्पादित कर रहे हैं । केवल जहाँ T1 पहले निष्पादित किया गया था factoryजो अशक्त था (इसलिए मान कैश नहीं है)। अन्यथा T2 और T3 को केवल संचित मूल्य मिल जाएगा (जो सुरक्षित होना चाहिए)। मुझे लगता है कि आसान समाधान को हटाना है, keyLocks.TryRemove(key, out locker)लेकिन फिर बड़ी संख्या में विभिन्न कुंजियों का उपयोग करके समवर्ती छाया एक मेमोरी लीक बन सकती है। अन्यथा हटाने से पहले एक कुंजी के लिए ताले गिनने के लिए कुछ तर्क जोड़ें, शायद एक सेमाफोर का उपयोग कर रहे हैं?
1919 को

13

बस, पावेल ने जो कहा उसकी प्रतिध्वनि करने के लिए, मेरा मानना ​​है कि यह इसे लिखने का सबसे सुरक्षित तरीका है

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

7
'ताला (यह) ` खराब है । आपको एक समर्पित लॉक ऑब्जेक्ट का उपयोग करना चाहिए जो आपके वर्ग के उपयोगकर्ताओं के लिए दृश्यमान नहीं है। सड़क के नीचे, किसी व्यक्ति ने लॉक करने के लिए कैश ऑब्जेक्ट का उपयोग करने का निर्णय लिया। वे इस बात से अनजान होंगे कि इसका उपयोग आंतरिक रूप से लॉकिंग उद्देश्यों के लिए किया जा रहा है, जिससे बदबू आ सकती है।
खर्च करें


2

मैं निम्नलिखित विस्तार विधि के साथ आया हूं:

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

मैंने @John Owen और @ user378380 दोनों उत्तरों का उपयोग किया है। मेरा समाधान आपको कैश के भीतर इंट और बूल मान संग्रहीत करने की अनुमति देता है।

अगर कोई त्रुटि है या क्या यह थोड़ा बेहतर लिखा जा सकता है तो कृपया मुझे सुधारें।


यह 5 मिनीट (60 * 5 = 300 सेकंड) की डिफ़ॉल्ट कैश लंबाई है।
nfplee

3
महान काम, लेकिन मैं एक मुद्दा देखता हूं: यदि आपके पास कई कैश हैं, तो वे सभी एक ही लॉक साझा करेंगे। इसे और अधिक मजबूत बनाने के लिए, दिए गए कैश से मिलान किए गए लॉक को पुनः प्राप्त करने के लिए एक शब्दकोश का उपयोग करें।
जोकूल

1

मैंने हाल ही में सही स्टेट बैग एक्सेस पैटर्न नामक एक पैटर्न देखा, जो इस पर स्पर्श करने के लिए लग रहा था।

मैंने इसे थ्रेड-सुरक्षित होने के लिए थोड़ा संशोधित किया।

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

क्या दो धागे दोनों के लिए (myList == अशक्त) के लिए एक सही परिणाम नहीं मिल सकता है? फिर, दोनों थ्रेड्स DAL.ListCustomers () को कॉल करते हैं और परिणाम को कैश में डालते हैं।
फ्रेंकडेलिक

4
लॉक के बाद आप कैश फिर से नहीं स्थानीय जाँच करने के लिए, की जरूरत है myListचर
orip

1
आपके संपादन से पहले यह वास्तव में ठीक था। यदि आप Insertअपवादों को रोकने के लिए उपयोग करते हैं तो कोई लॉक की आवश्यकता नहीं है, केवल अगर आप यह सुनिश्चित करना चाहते हैं कि DAL.ListCustomersएक बार कॉल किया जाए (हालांकि यदि परिणाम शून्य है, तो इसे हर बार कहा जाएगा)।
marapet


0

मैंने एक पुस्तकालय लिखा है जो उस विशेष मुद्दे को हल करता है: Rocks.Caching

इसके अलावा, मैंने इस समस्या के बारे में विवरण में बताया है और बताया कि यह यहाँ क्यों महत्वपूर्ण है


0

मैंने अधिक लचीलेपन के लिए @ user378380 का कोड संशोधित किया। TResult को वापस करने के बजाय अब विभिन्न प्रकारों को क्रम में स्वीकार करने के लिए ऑब्जेक्ट लौटाता है। लचीलेपन के लिए कुछ मापदंडों को जोड़ना। सारा विचार @ 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;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.