.NET मैमोरी कैश के उचित उपयोग के लिए लॉकिंग पैटर्न


115

मुझे लगता है कि इस कोड में समसामयिक मुद्दे हैं:

const string CacheKey = "CacheKey";
static string GetCachedData()
{
    string expensiveString =null;
    if (MemoryCache.Default.Contains(CacheKey))
    {
        expensiveString = MemoryCache.Default[CacheKey] as string;
    }
    else
    {
        CacheItemPolicy cip = new CacheItemPolicy()
        {
            AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
        };
        expensiveString = SomeHeavyAndExpensiveCalculation();
        MemoryCache.Default.Set(CacheKey, expensiveString, cip);
    }
    return expensiveString;
}

समसामयिक मुद्दे का कारण यह है कि कई थ्रेड्स एक शून्य कुंजी प्राप्त कर सकते हैं और फिर कैश में डेटा डालने का प्रयास कर सकते हैं।

इस कोड को संगति प्रमाण बनाने के लिए सबसे छोटा और साफ तरीका क्या होगा? मैं अपने कैश संबंधित कोड में एक अच्छे पैटर्न का पालन करना पसंद करता हूं। एक ऑनलाइन लेख के लिए एक लिंक एक बड़ी मदद होगी।

अपडेट करें:

मैं @Scott चेम्बरलेन के उत्तर के आधार पर इस कोड के साथ आया था। किसी को भी इस के साथ किसी भी प्रदर्शन या संगरोध मुद्दा मिल सकता है? यदि यह काम करता है, तो यह कोड और त्रुटियों की कई पंक्ति को बचाएगा।

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Caching;

namespace CachePoc
{
    class Program
    {
        static object everoneUseThisLockObject4CacheXYZ = new object();
        const string CacheXYZ = "CacheXYZ";
        static object everoneUseThisLockObject4CacheABC = new object();
        const string CacheABC = "CacheABC";

        static void Main(string[] args)
        {
            string xyzData = MemoryCacheHelper.GetCachedData<string>(CacheXYZ, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);
            string abcData = MemoryCacheHelper.GetCachedData<string>(CacheABC, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);
        }

        private static string SomeHeavyAndExpensiveXYZCalculation() {return "Expensive";}
        private static string SomeHeavyAndExpensiveABCCalculation() {return "Expensive";}

        public static class MemoryCacheHelper
        {
            public static T GetCachedData<T>(string cacheKey, object cacheLock, int cacheTimePolicyMinutes, Func<T> GetData)
                where T : class
            {
                //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
                T cachedData = MemoryCache.Default.Get(cacheKey, null) as T;

                if (cachedData != null)
                {
                    return cachedData;
                }

                lock (cacheLock)
                {
                    //Check to see if anyone wrote to the cache while we where waiting our turn to write the new value.
                    cachedData = MemoryCache.Default.Get(cacheKey, null) as T;

                    if (cachedData != null)
                    {
                        return cachedData;
                    }

                    //The value still did not exist so we now write it in to the cache.
                    CacheItemPolicy cip = new CacheItemPolicy()
                    {
                        AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(cacheTimePolicyMinutes))
                    };
                    cachedData = GetData();
                    MemoryCache.Default.Set(cacheKey, cachedData, cip);
                    return cachedData;
                }
            }
        }
    }
}

3
आप का उपयोग क्यों नहीं करते ReaderWriterLockSlim?
डार्थवेडर

2
मैं डार्थवेडर से सहमत हूं ... मुझे लगता है कि आप दुबले होंगे ReaderWriterLockSlim... लेकिन मैं इस तकनीक का इस्तेमाल try-finallyबयानों से बचने के लिए भी करूंगा ।
पोय

1
आपके अपडेट किए गए संस्करण के लिए, मैं अब एक भी कैशलॉक पर लॉक नहीं करूंगा, मैं इसके बजाय प्रति कुंजी लॉक करूंगा। यह आसानी से किया जा सकता है Dictionary<string, object>जहां कुंजी आपके द्वारा उपयोग की जाने वाली एक ही कुंजी है MemoryCacheऔर शब्दकोश में ऑब्जेक्ट सिर्फ एक बुनियादी है Objectजिसे आप लॉक करते हैं। हालाँकि, यह कहा जा रहा है, मैं आपको जॉन हन्ना के उत्तर के माध्यम से पढ़ता हूं। उचित प्रोफाइलिंग के बिना, आप लेट के दो उदाहरणों को SomeHeavyAndExpensiveCalculation()चलाने के बजाय लॉकिंग के साथ अपने कार्यक्रम को धीमा कर सकते हैं और एक परिणाम निकाल दिया जाता है।
स्कॉट चैंबरलेन

1
यह मुझे लगता है कि कैश के लिए महंगा मूल्य प्राप्त करने के बाद CacheItemPolicy बनाना अधिक सटीक होगा। सबसे खराब स्थिति में जैसे कि एक सारांश रिपोर्ट बनाने में जो "महंगी स्ट्रिंग" को वापस करने में 21 मिनट का समय लेती है (शायद पीडीएफ रिपोर्ट का फ़ाइल नाम युक्त) लौटाए जाने से पहले ही "समाप्त हो जाएगा"।
वंडरबर्ड

1
@Wonderbird अच्छा बिंदु, मैंने ऐसा करने के लिए अपना उत्तर अपडेट किया।
स्कॉट चैंबरलेन

जवाबों:


91

यह कोड की मेरी 2 पुनरावृति है। क्योंकि MemoryCacheथ्रेड सुरक्षित है जिसे आपको प्रारंभिक रीड पर लॉक करने की आवश्यकता नहीं है, आप बस पढ़ सकते हैं और यदि कैश शून्य हो जाता है, तो यह देखने के लिए लॉक चेक करें कि क्या आपको स्ट्रिंग बनाने की आवश्यकता है। यह कोड को बहुत सरल करता है।

const string CacheKey = "CacheKey";
static readonly object cacheLock = new object();
private static string GetCachedData()
{

    //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
    var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;

    if (cachedString != null)
    {
        return cachedString;
    }

    lock (cacheLock)
    {
        //Check to see if anyone wrote to the cache while we where waiting our turn to write the new value.
        cachedString = MemoryCache.Default.Get(CacheKey, null) as string;

        if (cachedString != null)
        {
            return cachedString;
        }

        //The value still did not exist so we now write it in to the cache.
        var expensiveString = SomeHeavyAndExpensiveCalculation();
        CacheItemPolicy cip = new CacheItemPolicy()
                              {
                                  AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
                              };
        MemoryCache.Default.Set(CacheKey, expensiveString, cip);
        return expensiveString;
    }
}

संपादित करें : नीचे दिया गया कोड अनावश्यक है लेकिन मैं इसे मूल विधि दिखाने के लिए छोड़ना चाहता था। यह भविष्य के आगंतुकों के लिए उपयोगी हो सकता है जो एक अलग संग्रह का उपयोग कर रहे हैं जिसमें थ्रेड सुरक्षित रीड हैं लेकिन गैर-थ्रेड सुरक्षित लिखते हैं ( System.Collectionsनामस्थान के तहत लगभग सभी वर्ग ऐसा है)।

यहां बताया गया है कि मैं इसका उपयोग ReaderWriterLockSlimएक्सेस की सुरक्षा के लिए कैसे करूंगा । आपको यह देखने के लिए एक प्रकार की " डबल चेकिंग लॉकिंग " करने की ज़रूरत है कि क्या कोई और कैश्ड आइटम बनाए जबकि हम लॉक लेने के लिए प्रतीक्षा कर रहे हैं।

const string CacheKey = "CacheKey";
static readonly ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
static string GetCachedData()
{
    //First we do a read lock to see if it already exists, this allows multiple readers at the same time.
    cacheLock.EnterReadLock();
    try
    {
        //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
        var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;

        if (cachedString != null)
        {
            return cachedString;
        }
    }
    finally
    {
        cacheLock.ExitReadLock();
    }

    //Only one UpgradeableReadLock can exist at one time, but it can co-exist with many ReadLocks
    cacheLock.EnterUpgradeableReadLock();
    try
    {
        //We need to check again to see if the string was created while we where waiting to enter the EnterUpgradeableReadLock
        var cachedString = MemoryCache.Default.Get(CacheKey, null) as string;

        if (cachedString != null)
        {
            return cachedString;
        }

        //The entry still does not exist so we need to create it and enter the write lock
        var expensiveString = SomeHeavyAndExpensiveCalculation();
        cacheLock.EnterWriteLock(); //This will block till all the Readers flush.
        try
        {
            CacheItemPolicy cip = new CacheItemPolicy()
            {
                AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
            };
            MemoryCache.Default.Set(CacheKey, expensiveString, cip);
            return expensiveString;
        }
        finally 
        {
            cacheLock.ExitWriteLock();
        }
    }
    finally
    {
        cacheLock.ExitUpgradeableReadLock();
    }
}

1
@DarthVader किस तरह से उपरोक्त कोड काम नहीं करेगा? यह भी कड़ाई से "डबल चेक लॉकिंग" नहीं है मैं सिर्फ एक समान पैटर्न का पालन कर रहा हूं और यह सबसे अच्छा तरीका था जिसे मैं इसका वर्णन करने के बारे में सोच सकता था। इसीलिए मैंने कहा कि यह एक तरह का दोहरा चेक लॉकिंग था।
स्कॉट चेम्बरलेन

मैंने आपके कोड पर टिप्पणी नहीं की। मैं टिप्पणी कर रहा था कि डबल चेक लॉकिंग काम नहीं करता है। आपका कोड ठीक है।
डार्थवेडर

1
मुझे यह देखना मुश्किल है कि इस तरह के लॉकिंग और इस तरह के स्टोरेज की क्या स्थितियाँ हैं, हालांकि यह समझ में आता है: यदि आप सभी क्रिएशंस को लॉक कर रहे हैं तो एक MemoryCacheमौके में जाने वाली वैल्यूज़ कम से कम उन दो चीजों में से एक थीं।
जॉन हैना

@ScottChamberlain सिर्फ इस कोड को देख रहा है, और क्या यह ताला और कोशिश ब्लॉक के अधिग्रहण के बीच फेंके जाने वाले अपवाद के लिए अतिसंवेदनशील नहीं है। C # इन नटशेल के लेखक यहाँ इस पर चर्चा करते हैं, albahari.com/threading/part2.aspx#_MonitorEnter_and_MonitorExit
BrutalSimplicity

9
इस कोड का एक नकारात्मक पहलू यह है कि CacheKey "A" CacheKey "B" के लिए अनुरोध को रोक देगा यदि दोनों को अभी तक कैश नहीं किया गया है। इसे हल करने के लिए आप एक समवर्ती छाया का उपयोग कर सकते हैं <string, object> जिसमें आप cachekeys को लॉक करने के लिए स्टोर करते हैं
MichaelD

44

एक खुला स्रोत पुस्तकालय है [अस्वीकरण: जो मैंने लिखा है]: LazyCache कि IMO आपकी आवश्यकता को कोड की दो पंक्तियों के साथ कवर करता है:

IAppCache cache = new CachingService();
var cachedResults = cache.GetOrAdd("CacheKey", 
  () => SomeHeavyAndExpensiveCalculation());

यह डिफ़ॉल्ट रूप से लॉकिंग में बनाया गया है, इसलिए कैश करने योग्य विधि केवल कैश मिस के अनुसार एक बार निष्पादित होगी, और यह एक लैम्ब्डा का उपयोग करती है ताकि आप एक बार में "प्राप्त या जोड़" सकें। यह 20 मिनट के लिए समय सीमा समाप्त करने के लिए चूक है।

यहां तक ​​कि एक NuGet पैकेज ;)


4
कैशिंग का डापर।
चार्ल्स बर्न्स

3
यह मुझे एक आलसी डेवलपर बनाता है जो इसे सबसे अच्छा उत्तर बनाता है!
jdnew18

वर्थ ने लेख का उल्लेख करते हुए कहा कि LazyCache अंक के लिए github पृष्ठ इसके पीछे के कारणों के लिए काफी अच्छा पढ़ा गया है। alastaircrabtree.com/…
राफेल मर्लिन

2
क्या यह प्रति कुंजी या प्रति कैश लॉक है?
jjxtra

1
@DirkBoer नहीं यह अवरुद्ध नहीं होगा क्योंकि जिस तरह से ताले और आलसी का उपयोग lazycache में किया जाता है
alastairtree

30

मैंने इस मुद्दे को MemoryCache पर AddOrGetExisting विधि का उपयोग करके और आलसी प्रारंभ के उपयोग से हल किया है ।

अनिवार्य रूप से, मेरा कोड कुछ इस तरह दिखता है:

static string GetCachedData(string key, DateTimeOffset offset)
{
    Lazy<String> lazyObject = new Lazy<String>(() => SomeHeavyAndExpensiveCalculationThatReturnsAString());
    var returnedLazyObject = MemoryCache.Default.AddOrGetExisting(key, lazyObject, offset); 
    if (returnedLazyObject == null)
       return lazyObject.Value;
    return ((Lazy<String>) returnedLazyObject).Value;
}

सबसे खराब स्थिति यह है कि आप एक ही Lazyवस्तु को दो बार बनाते हैं । लेकिन यह बहुत तुच्छ है। AddOrGetExistingगारंटियों का उपयोग जो आपको केवल Lazyवस्तु का एक उदाहरण मिलेगा , और इसलिए आपको केवल एक बार महंगी इनिशियलाइज़ेशन विधि को कॉल करने की गारंटी है।


4
इस प्रकार के दृष्टिकोण के साथ समस्या यह है कि आप अमान्य डेटा सम्मिलित कर सकते हैं। यदि SomeHeavyAndExpensiveCalculationThatResultsAString()एक अपवाद फेंक दिया है, तो यह कैश में फंस गया है। यहां तक ​​कि क्षणिक अपवादों के साथ कैश हो जाएगा Lazy<T>: msdn.microsoft.com/en-us/library/vstudio/dd642331.aspx
स्कॉट

2
हालांकि यह सच है कि आलसी <टी> एक त्रुटि लौटा सकता है यदि आरंभीकरण अपवाद विफल रहता है, तो यह पता लगाने के लिए एक बहुत आसान बात है। फिर आप किसी भी आलसी <टी> को निकाल सकते हैं जो कैश से एक त्रुटि का समाधान करता है, एक नया आलसी <टी> बनाएं, इसे कैश में डालें और इसे हल करें। हमारे अपने कोड में, हम कुछ ऐसा ही करते हैं। हम एक त्रुटि को फेंकने से पहले कई बार सेट करते हैं।
कीथ

12
AddOrGetEx मौजूदा रिटर्न शून्य है यदि आइटम मौजूद नहीं था, तो आपको जांच करनी चाहिए और उस मामले में आलसी होना चाहिए।
Gian Marco

1
LazyThreadSafetyMode.PublicationOnly का उपयोग अपवादों के कैशिंग से होगा।
क्लेमेंट

2
इस ब्लॉग पोस्ट में टिप्पणियों के अनुसार यदि कैश प्रविष्टि को प्रारंभ करना बहुत महंगा है, तो यह केवल एक अपवाद पर निकाल देना बेहतर है (जैसा कि ब्लॉग पोस्ट में उदाहरण में दिखाया गया है) PublicationOnly का उपयोग करने के बजाय, क्योंकि सभी संभावना है कि सूत्र एक ही समय में इनिशियलाइज़र को कॉल कर सकते हैं।
ई.पू.

15

मुझे लगता है कि इस कोड में समसामयिक मुद्दे हैं:

वास्तव में, यह संभवतया ठीक है, हालांकि एक संभावित सुधार के साथ।

अब, सामान्य रूप में, जहां हमारे पास कई थ्रेड हैं, जो पहले उपयोग पर एक साझा मूल्य सेट करते हैं, प्राप्त होने वाले मूल्य पर लॉक नहीं करते हैं और सेट कर सकते हैं:

  1. विनाशकारी - अन्य कोड मान लेंगे कि केवल एक उदाहरण मौजूद है।
  2. विनाशकारी - कोड जो आवृत्ति प्राप्त करता है वह केवल एक (या शायद एक निश्चित छोटी संख्या) समवर्ती संचालन को सहन नहीं कर सकता है।
  3. विनाशकारी - भंडारण का साधन थ्रेड-सेफ़ नहीं है (जैसे किसी शब्दकोश में दो धागे जोड़ना और आपको हर तरह की गंदी त्रुटियाँ मिल सकती हैं)।
  4. उप-इष्टतम - समग्र प्रदर्शन से भी बदतर है अगर लॉकिंग ने केवल एक धागा सुनिश्चित किया था कि मूल्य प्राप्त करने का काम किया था।
  5. इष्टतम - कई थ्रेड्स को निरर्थक कार्य करने की लागत इसे रोकने की लागत से कम है, खासकर जब से यह केवल अपेक्षाकृत संक्षिप्त अवधि के दौरान हो सकता है।

हालाँकि, यहाँ विचार है कि MemoryCacheप्रविष्टियों को बेदखल कर सकते हैं:

  1. यदि यह एक से अधिक उदाहरणों के लिए विनाशकारी है तो MemoryCacheगलत दृष्टिकोण है।
  2. यदि आपको एक साथ निर्माण रोकना चाहिए, तो आपको सृजन के बिंदु पर ऐसा करना चाहिए।
  3. MemoryCache उस वस्तु तक पहुंच के संदर्भ में धागा-सुरक्षित है, इसलिए यहां कोई चिंता का विषय नहीं है।

इन दोनों संभावनाओं के बारे में निश्चित रूप से सोचा जाना चाहिए, हालांकि एक ही स्ट्रिंग के दो उदाहरण होने का एकमात्र समय एक समस्या हो सकती है यदि आप बहुत विशेष अनुकूलन कर रहे हैं जो यहां लागू नहीं होते हैं *।

इसलिए, हम संभावनाओं से बचे हैं:

  1. डुप्लिकेट कॉल की लागत से बचने के लिए यह सस्ता है SomeHeavyAndExpensiveCalculation()
  2. यह सस्ता है डुप्लिकेट कॉल की लागत से बचने के लिए नहीं SomeHeavyAndExpensiveCalculation()

और यह काम करना मुश्किल हो सकता है (वास्तव में, जिस तरह की चीज़ को आप इसे काम कर सकते हैं मानने की बजाय प्रोफाइलिंग के लायक है)। यह यहाँ ध्यान देने योग्य है, हालांकि सम्मिलित करने पर लॉक करने के सबसे स्पष्ट तरीके कैश के सभी अतिरिक्त को रोकेंगे , जिनमें असंबंधित हैं।

इसका मतलब यह है कि अगर हमारे पास 50 धागे हैं जो 50 अलग-अलग मूल्य निर्धारित करने की कोशिश कर रहे हैं, तो हमें सभी 50 धागे एक-दूसरे पर इंतजार करना होगा, भले ही वे एक ही गणना करने के लिए भी नहीं जा रहे थे।

इस तरह, आप शायद आपके पास उस कोड के साथ बेहतर हैं, जो कोड-रेस से बचता है, जो रेस-कंडीशन से बचता है, और यदि रेस-कंडीशन एक समस्या है, तो आपको इसकी संभावना है कि या तो आपको कहीं और संभालना है, या एक अलग की आवश्यकता है पुरानी प्रविष्टियों को निष्कासित करने वाली एक से अधिक कैशिंग रणनीति that।

एक चीज जो मैं बदलूंगा वह यह है कि मैं कॉल को Set()एक के साथ बदल दूंगा AddOrGetExisting()। ऊपर से यह स्पष्ट होना चाहिए कि यह संभवतः आवश्यक नहीं है, लेकिन यह नव प्राप्त वस्तु को एकत्र करने की अनुमति देगा, समग्र मेमोरी उपयोग को कम करेगा और उच्च पीढ़ी के संग्रह के लिए कम पीढ़ी के उच्च अनुपात की अनुमति देगा।

तो हाँ, आप कंसीडर को रोकने के लिए डबल-लॉकिंग का उपयोग कर सकते हैं, लेकिन या तो कंसीलर वास्तव में कोई समस्या नहीं है, या आपके मान गलत तरीके से स्टोर हो रहे हैं, या स्टोर पर डबल-लॉक करना इसे हल करने का सबसे अच्छा तरीका नहीं होगा। ।

* यदि आपको पता है कि स्ट्रिंग्स के सेट में से प्रत्येक एक ही मौजूद है, तो आप समानता तुलनाओं को अनुकूलित कर सकते हैं, जो कि एक ही समय में स्ट्रिंग की दो प्रतियाँ होने के बजाय केवल उप-इष्टतम की तुलना में गलत हो सकती हैं, लेकिन आप करना चाहते हैं समझ बनाने के लिए बहुत अलग प्रकार की कैशिंग। जैसे कि XmlReaderआंतरिक रूप से।

, काफी संभावना है कि अनिश्चित काल तक स्टोर करने वाला या कमजोर संदर्भों का उपयोग करने वाला एक है तो यह केवल मौजूदा प्रविष्टियों को निष्कासित करेगा यदि कोई मौजूदा उपयोग नहीं हैं।


1

ग्लोबल लॉक से बचने के लिए, आप मेमोरी के उपयोग में विस्फोट किए बिना, एक लॉक प्रति कुंजी को लागू करने के लिए सिंगलटन कैश का उपयोग कर सकते हैं (लॉक ऑब्जेक्ट तब हटाए जाते हैं जब कोई संदर्भ नहीं दिया जाता है, और अधिग्रहण / रिलीज थ्रेड सुरक्षित गारंटी है कि केवल 1 उदाहरण कभी भी उपयोग के माध्यम से तुलना में है और स्वैप)।

इसका उपयोग इस तरह दिखता है:

SingletonCache<string, object> keyLocks = new SingletonCache<string, object>();

const string CacheKey = "CacheKey";
static string GetCachedData()
{
    string expensiveString =null;
    if (MemoryCache.Default.Contains(CacheKey))
    {
        return MemoryCache.Default[CacheKey] as string;
    }

    // double checked lock
    using (var lifetime = keyLocks.Acquire(url))
    {
        lock (lifetime.Value)
        {
           if (MemoryCache.Default.Contains(CacheKey))
           {
              return MemoryCache.Default[CacheKey] as string;
           }

           cacheItemPolicy cip = new CacheItemPolicy()
           {
              AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
           };
           expensiveString = SomeHeavyAndExpensiveCalculation();
           MemoryCache.Default.Set(CacheKey, expensiveString, cip);
           return expensiveString;
        }
    }      
}

कोड यहाँ GitHub पर है: https://github.com/bitfaster/BitFaster.Caching

Install-Package BitFaster.Caching

एक LRU कार्यान्वयन भी है जो मेमोरीकेच की तुलना में हल्का है, और इसके कई फायदे हैं - तेजी से समवर्ती पढ़ता है और लिखता है, बाध्य आकार, कोई पृष्ठभूमि धागा, आंतरिक पूर्ण काउंटर आदि (अस्वीकरण, मैंने इसे लिखा है)।


0

MemoryCache का कंसोल उदाहरण , "साधारण श्रेणी की वस्तुओं को कैसे सहेजें / प्राप्त करें"

शुरू करने और दबाने के बाद उत्पादन Any keyको छोड़कर Esc:

कैश की बचत!
कैश से हो रही है!
कोई १
कोई २

    class Some
    {
        public String text { get; set; }

        public Some(String text)
        {
            this.text = text;
        }

        public override string ToString()
        {
            return text;
        }
    }

    public static MemoryCache cache = new MemoryCache("cache");

    public static string cache_name = "mycache";

    static void Main(string[] args)
    {

        Some some1 = new Some("some1");
        Some some2 = new Some("some2");

        List<Some> list = new List<Some>();
        list.Add(some1);
        list.Add(some2);

        do {

            if (cache.Contains(cache_name))
            {
                Console.WriteLine("Getting from cache!");
                List<Some> list_c = cache.Get(cache_name) as List<Some>;
                foreach (Some s in list_c) Console.WriteLine(s);
            }
            else
            {
                Console.WriteLine("Saving to cache!");
                cache.Set(cache_name, list, DateTime.Now.AddMinutes(10));                   
            }

        } while (Console.ReadKey(true).Key != ConsoleKey.Escape);

    }

0
public interface ILazyCacheProvider : IAppCache
{
    /// <summary>
    /// Get data loaded - after allways throw cached result (even when data is older then needed) but very fast!
    /// </summary>
    /// <param name="key"></param>
    /// <param name="getData"></param>
    /// <param name="slidingExpiration"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    T GetOrAddPermanent<T>(string key, Func<T> getData, TimeSpan slidingExpiration);
}

/// <summary>
/// Initialize LazyCache in runtime
/// </summary>
public class LazzyCacheProvider: CachingService, ILazyCacheProvider
{
    private readonly Logger _logger = LogManager.GetLogger("MemCashe");
    private readonly Hashtable _hash = new Hashtable();
    private readonly List<string>  _reloader = new List<string>();
    private readonly ConcurrentDictionary<string, DateTime> _lastLoad = new ConcurrentDictionary<string, DateTime>();  


    T ILazyCacheProvider.GetOrAddPermanent<T>(string dataKey, Func<T> getData, TimeSpan slidingExpiration)
    {
        var currentPrincipal = Thread.CurrentPrincipal;
        if (!ObjectCache.Contains(dataKey) && !_hash.Contains(dataKey))
        {
            _hash[dataKey] = null;
            _logger.Debug($"{dataKey} - first start");
            _lastLoad[dataKey] = DateTime.Now;
            _hash[dataKey] = ((object)GetOrAdd(dataKey, getData, slidingExpiration)).CloneObject();
            _lastLoad[dataKey] = DateTime.Now;
           _logger.Debug($"{dataKey} - first");
        }
        else
        {
            if ((!ObjectCache.Contains(dataKey) || _lastLoad[dataKey].AddMinutes(slidingExpiration.Minutes) < DateTime.Now) && _hash[dataKey] != null)
                Task.Run(() =>
                {
                    if (_reloader.Contains(dataKey)) return;
                    lock (_reloader)
                    {
                        if (ObjectCache.Contains(dataKey))
                        {
                            if(_lastLoad[dataKey].AddMinutes(slidingExpiration.Minutes) > DateTime.Now)
                                return;
                            _lastLoad[dataKey] = DateTime.Now;
                            Remove(dataKey);
                        }
                        _reloader.Add(dataKey);
                        Thread.CurrentPrincipal = currentPrincipal;
                        _logger.Debug($"{dataKey} - reload start");
                        _hash[dataKey] = ((object)GetOrAdd(dataKey, getData, slidingExpiration)).CloneObject();
                        _logger.Debug($"{dataKey} - reload");
                        _reloader.Remove(dataKey);
                    }
                });
        }
        if (_hash[dataKey] != null) return (T) (_hash[dataKey]);

        _logger.Debug($"{dataKey} - dummy start");
        var data = GetOrAdd(dataKey, getData, slidingExpiration);
        _logger.Debug($"{dataKey} - dummy");
        return (T)((object)data).CloneObject();
    }
}

बहुत तेज़ LazyCache :) मैंने यह कोड REST API रिपॉजिटरी के लिए लिखा था।
Art24war

0

यह थोड़ा देर हो चुकी है, हालांकि ... पूर्ण कार्यान्वयन:

    [HttpGet]
    public async Task<HttpResponseMessage> GetPageFromUriOrBody(RequestQuery requestQuery)
    {
        log(nameof(GetPageFromUriOrBody), nameof(requestQuery));
        var responseResult = await _requestQueryCache.GetOrCreate(
            nameof(GetPageFromUriOrBody)
            , requestQuery
            , (x) => getPageContent(x).Result);
        return Request.CreateResponse(System.Net.HttpStatusCode.Accepted, responseResult);
    }
    static MemoryCacheWithPolicy<RequestQuery, string> _requestQueryCache = new MemoryCacheWithPolicy<RequestQuery, string>();

यहाँ getPageContentहस्ताक्षर है:

async Task<string> getPageContent(RequestQuery requestQuery);

और यहाँ MemoryCacheWithPolicyकार्यान्वयन है:

public class MemoryCacheWithPolicy<TParameter, TResult>
{
    static ILogger _nlogger = new AppLogger().Logger;
    private MemoryCache _cache = new MemoryCache(new MemoryCacheOptions() 
    {
        //Size limit amount: this is actually a memory size limit value!
        SizeLimit = 1024 
    });

    /// <summary>
    /// Gets or creates a new memory cache record for a main data
    /// along with parameter data that is assocciated with main main.
    /// </summary>
    /// <param name="key">Main data cache memory key.</param>
    /// <param name="param">Parameter model that assocciated to main model (request result).</param>
    /// <param name="createCacheData">A delegate to create a new main data to cache.</param>
    /// <returns></returns>
    public async Task<TResult> GetOrCreate(object key, TParameter param, Func<TParameter, TResult> createCacheData)
    {
        // this key is used for param cache memory.
        var paramKey = key + nameof(param);

        if (!_cache.TryGetValue(key, out TResult cacheEntry))
        {
            // key is not in the cache, create data through the delegate.
            cacheEntry = createCacheData(param);
            createMemoryCache(key, cacheEntry, paramKey, param);

            _nlogger.Warn(" cache is created.");
        }
        else
        {
            // data is chached so far..., check if param model is same (or changed)?
            if(!_cache.TryGetValue(paramKey, out TParameter cacheParam))
            {
                //exception: this case should not happened!
            }

            if (!cacheParam.Equals(param))
            {
                // request param is changed, create data through the delegate.
                cacheEntry = createCacheData(param);
                createMemoryCache(key, cacheEntry, paramKey, param);
                _nlogger.Warn(" cache is re-created (param model has been changed).");
            }
            else
            {
                _nlogger.Trace(" cache is used.");
            }

        }
        return await Task.FromResult<TResult>(cacheEntry);
    }
    MemoryCacheEntryOptions createMemoryCacheEntryOptions(TimeSpan slidingOffset, TimeSpan relativeOffset)
    {
        // Cache data within [slidingOffset] seconds, 
        // request new result after [relativeOffset] seconds.
        return new MemoryCacheEntryOptions()

            // Size amount: this is actually an entry count per 
            // key limit value! not an actual memory size value!
            .SetSize(1)

            // Priority on removing when reaching size limit (memory pressure)
            .SetPriority(CacheItemPriority.High)

            // Keep in cache for this amount of time, reset it if accessed.
            .SetSlidingExpiration(slidingOffset)

            // Remove from cache after this time, regardless of sliding expiration
            .SetAbsoluteExpiration(relativeOffset);
        //
    }
    void createMemoryCache(object key, TResult cacheEntry, object paramKey, TParameter param)
    {
        // Cache data within 2 seconds, 
        // request new result after 5 seconds.
        var cacheEntryOptions = createMemoryCacheEntryOptions(
            TimeSpan.FromSeconds(2)
            , TimeSpan.FromSeconds(5));

        // Save data in cache.
        _cache.Set(key, cacheEntry, cacheEntryOptions);

        // Save param in cache.
        _cache.Set(paramKey, param, cacheEntryOptions);
    }
    void checkCacheEntry<T>(object key, string name)
    {
        _cache.TryGetValue(key, out T value);
        _nlogger.Fatal("Key: {0}, Name: {1}, Value: {2}", key, name, value);
    }
}

nloggerव्यवहार nLogका पता लगाने के लिए सिर्फ वस्तु है MemoryCacheWithPolicy। मैं मेमोरी कैश को फिर से बनाता हूं यदि अनुरोध ऑब्जेक्ट ( RequestQuery requestQuery) को प्रतिनिधि ( Func<TParameter, TResult> createCacheData) के माध्यम से बदल दिया जाता है या स्लाइडिंग या निरपेक्ष समय उनकी सीमा तक पहुंच जाता है। ध्यान दें कि सब कुछ async भी है;)


हो सकता है कि आपका उत्तर इस प्रश्न से अधिक संबंधित हो: Async
थ्रेडसेफ़, मेमोरीकैच

मुझे ऐसा लगता है, लेकिन अभी भी उपयोगी अनुभव विनिमय;)
सैम सैरियन

0

यह चुनना मुश्किल है कि कौन सा बेहतर है; ताला या ReaderWriterLockSlim। आपको संख्याओं और अनुपातों आदि को पढ़ने और लिखने के वास्तविक विश्व आँकड़ों की आवश्यकता है।

लेकिन अगर आप मानते हैं कि "लॉक" का उपयोग करना सही तरीका है। फिर यहां विभिन्न आवश्यकताओं के लिए एक अलग समाधान है। मैं कोड में एलन जू के समाधान को भी शामिल करता हूं। क्योंकि दोनों की जरूरत अलग-अलग हो सकती है।

यहां आवश्यकताएं हैं, इस समाधान के लिए मुझे ड्राइविंग:

  1. आप किसी कारण से 'गेटडाटा' फ़ंक्शन की आपूर्ति नहीं कर सकते हैं या नहीं कर सकते हैं। शायद 'गेटडाटा' फंक्शन किसी अन्य वर्ग में एक भारी कंस्ट्रक्टर के साथ स्थित है और आप यह सुनिश्चित नहीं करना चाहते हैं कि यह तब तक एक उदाहरण न बन जाए।
  2. आपको आवेदन के विभिन्न स्थानों / स्तरों से एक ही कैश किए गए डेटा तक पहुंचने की आवश्यकता है। और उन विभिन्न स्थानों में एक ही लॉकर ऑब्जेक्ट तक पहुंच नहीं है।
  3. आपके पास निरंतर कैश कुंजी नहीं है। उदाहरण के लिए; sessionId कैश कुंजी के साथ कुछ डेटा को कैशिंग करने की आवश्यकता है।

कोड:

using System;
using System.Runtime.Caching;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace CachePoc
{
    class Program
    {
        static object everoneUseThisLockObject4CacheXYZ = new object();
        const string CacheXYZ = "CacheXYZ";
        static object everoneUseThisLockObject4CacheABC = new object();
        const string CacheABC = "CacheABC";

        static void Main(string[] args)
        {
            //Allan Xu's usage
            string xyzData = MemoryCacheHelper.GetCachedDataOrAdd<string>(CacheXYZ, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);
            string abcData = MemoryCacheHelper.GetCachedDataOrAdd<string>(CacheABC, everoneUseThisLockObject4CacheXYZ, 20, SomeHeavyAndExpensiveXYZCalculation);

            //My usage
            string sessionId = System.Web.HttpContext.Current.Session["CurrentUser.SessionId"].ToString();
            string yvz = MemoryCacheHelper.GetCachedData<string>(sessionId);
            if (string.IsNullOrWhiteSpace(yvz))
            {
                object locker = MemoryCacheHelper.GetLocker(sessionId);
                lock (locker)
                {
                    yvz = MemoryCacheHelper.GetCachedData<string>(sessionId);
                    if (string.IsNullOrWhiteSpace(yvz))
                    {
                        DatabaseRepositoryWithHeavyConstructorOverHead dbRepo = new DatabaseRepositoryWithHeavyConstructorOverHead();
                        yvz = dbRepo.GetDataExpensiveDataForSession(sessionId);
                        MemoryCacheHelper.AddDataToCache(sessionId, yvz, 5);
                    }
                }
            }
        }


        private static string SomeHeavyAndExpensiveXYZCalculation() { return "Expensive"; }
        private static string SomeHeavyAndExpensiveABCCalculation() { return "Expensive"; }

        public static class MemoryCacheHelper
        {
            //Allan Xu's solution
            public static T GetCachedDataOrAdd<T>(string cacheKey, object cacheLock, int minutesToExpire, Func<T> GetData) where T : class
            {
                //Returns null if the string does not exist, prevents a race condition where the cache invalidates between the contains check and the retreival.
                T cachedData = MemoryCache.Default.Get(cacheKey, null) as T;

                if (cachedData != null)
                    return cachedData;

                lock (cacheLock)
                {
                    //Check to see if anyone wrote to the cache while we where waiting our turn to write the new value.
                    cachedData = MemoryCache.Default.Get(cacheKey, null) as T;

                    if (cachedData != null)
                        return cachedData;

                    cachedData = GetData();
                    MemoryCache.Default.Set(cacheKey, cachedData, DateTime.Now.AddMinutes(minutesToExpire));
                    return cachedData;
                }
            }

            #region "My Solution"

            readonly static ConcurrentDictionary<string, object> Lockers = new ConcurrentDictionary<string, object>();
            public static object GetLocker(string cacheKey)
            {
                CleanupLockers();

                return Lockers.GetOrAdd(cacheKey, item => (cacheKey, new object()));
            }

            public static T GetCachedData<T>(string cacheKey) where T : class
            {
                CleanupLockers();

                T cachedData = MemoryCache.Default.Get(cacheKey) as T;
                return cachedData;
            }

            public static void AddDataToCache(string cacheKey, object value, int cacheTimePolicyMinutes)
            {
                CleanupLockers();

                MemoryCache.Default.Add(cacheKey, value, DateTimeOffset.Now.AddMinutes(cacheTimePolicyMinutes));
            }

            static DateTimeOffset lastCleanUpTime = DateTimeOffset.MinValue;
            static void CleanupLockers()
            {
                if (DateTimeOffset.Now.Subtract(lastCleanUpTime).TotalMinutes > 1)
                {
                    lock (Lockers)//maybe a better locker is needed?
                    {
                        try//bypass exceptions
                        {
                            List<string> lockersToRemove = new List<string>();
                            foreach (var locker in Lockers)
                            {
                                if (!MemoryCache.Default.Contains(locker.Key))
                                    lockersToRemove.Add(locker.Key);
                            }

                            object dummy;
                            foreach (string lockerKey in lockersToRemove)
                                Lockers.TryRemove(lockerKey, out dummy);

                            lastCleanUpTime = DateTimeOffset.Now;
                        }
                        catch (Exception)
                        { }
                    }
                }

            }
            #endregion
        }
    }

    class DatabaseRepositoryWithHeavyConstructorOverHead
    {
        internal string GetDataExpensiveDataForSession(string sessionId)
        {
            return "Expensive data from database";
        }
    }

}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.