正确使用.NET的MemoryCache的锁定模式 [英] Locking pattern for proper use of .NET MemoryCache

查看:189
本文介绍了正确使用.NET的MemoryCache的锁定模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想这code的并发问题:

 常量字符串CacheKey =CacheKey;
静态字符串GetCachedData()
{
    字符串expensiveString = NULL;
    如果(MemoryCache.Default.Contains(CacheKey))
    {
        expensiveString = MemoryCache.Default [CacheKey]作为字符串;
    }
    其他
    {
        CacheItemPolicy CIP =新CacheItemPolicy()
        {
            AbsoluteExpiration =新的DateTimeOffset(DateTime.Now.AddMinutes(20))
        };
        expensiveString = SomeHeavyAndExpensiveCalculation();
        MemoryCache.Default.Set(CacheKey,expensiveString,CIP);
    }
    返回expensiveString;
}

的原因并发的问题是,多个线程可以得到一个空键,然后尝试将数据插入到高速缓存中。

什么是使这个code并发证明最短,干净的方式?我喜欢追随在我的缓存相关的code的良好格局。到网上的文章的链接将是一个很大的帮助。

更新:

我想出了基于@Scott张伯伦的回答这个code。任何人都可以找到任何与此性能或并发性问题?
如果一切正常,这将节省code和错误的许多行。

 使用系统;
使用System.Collections.Generic;
使用System.Linq的;
使用System.Text;
使用System.Threading.Tasks;
使用System.Runtime.Caching;命名空间CachePoc
{
    类节目
    {
        静态对象everoneUseThisLockObject4CacheXYZ =新的对象();
        常量字符串CacheXYZ =CacheXYZ;
        静态对象everoneUseThisLockObject4CacheABC =新的对象();
        常量字符串CacheABC =CacheABC;        静态无效的主要(字串[] args)
        {
            字符串xyzData = MemoryCacheHelper.GetCachedData<串GT;(CacheXYZ,everoneUseThisLockObject4CacheXYZ,20,SomeHeavyAndExpensiveXYZCalculation);
            字符串abcData = MemoryCacheHelper.GetCachedData<串GT;(CacheABC,everoneUseThisLockObject4CacheXYZ,20,SomeHeavyAndExpensiveXYZCalculation);
        }        私人静态字符串SomeHeavyAndExpensiveXYZCalculation(){返回贵;}
        私人静态字符串SomeHeavyAndExpensiveABCCalculation(){返回贵;}        公共静态类MemoryCacheHelper
        {
            公共静态ŧGetCachedData< T>(字符串cacheKey,对象cacheLock,诠释cacheTimePolicyMinutes,Func键< T>的GetData)
                其中T:类
            {
                //如果字符串不存在返回null,prevents那里的缓存失效之间的包含检查和retreival的竞争条件。
                ŧcachedData = MemoryCache.Default.Get(cacheKey,NULL)为T;                如果(cachedData!= NULL)
                {
                    返回cachedData;
                }                锁定(cacheLock)
                {
                    //检查,看是否有人写信到缓存中,而我们在那里等着轮到我们写新值。
                    cachedData = MemoryCache.Default.Get(cacheKey,NULL)为T;                    如果(cachedData!= NULL)
                    {
                        返回cachedData;
                    }                    //该值仍然是不存在的,所以我们现在缓存写。
                    CacheItemPolicy CIP =新CacheItemPolicy()
                    {
                        AbsoluteExpiration =新的DateTimeOffset(DateTime.Now.AddMinutes(cacheTimePolicyMinutes))
                    };
                    cachedData =的GetData();
                    MemoryCache.Default.Set(cacheKey,cachedData,CIP);
                    返回cachedData;
                }
            }
        }
    }
}


解决方案

这是我的code的第二个迭代。因为的MemoryCache 是线程安全的,你并不需要锁定的初始读取,你可以阅读,如果缓存返回null,做锁检查,看是否需要创建的字符串。它大大简化了code。

 常量字符串CacheKey =CacheKey;
静态只读对象cacheLock =新的对象();
私人静态字符串GetCachedData()
{    //如果字符串不存在返回null,prevents那里的缓存失效之间的包含检查和retreival的竞争条件。
    VAR cachedString = MemoryCache.Default.Get(CacheKey,NULL)作为字符串;    如果(cachedString!= NULL)
    {
        返回cachedString;
    }    锁定(cacheLock)
    {
        //检查,看是否有人写信到缓存中,而我们在那里等着轮到我们写新值。
        cachedString = MemoryCache.Default.Get(CacheKey,NULL)作为字符串;        如果(cachedString!= NULL)
        {
            返回cachedString;
        }        //该值仍然是不存在的,所以我们现在缓存写。
        变种expensiveString = SomeHeavyAndExpensiveCalculation();
        CacheItemPolicy CIP =新CacheItemPolicy()
                              {
                                  AbsoluteExpiration =新的DateTimeOffset(DateTime.Now.AddMinutes(20))
                              };
        MemoryCache.Default.Set(CacheKey,expensiveString,CIP);
        返回expensiveString;
    }
}


修改:以下code是不必要的,但我想离开它显示的原始方法。这我是谁正在使用不同的集合,具有线程安全的未来有益的游客,但读取非线程安全的写操作(几乎所有的 System.Collections中命名空间下的类是像这一点)。

下面是我如何使用 ReaderWriterLockSlim 保护访问做。你需要做一种双重检查锁定来看看别人创建的缓存项,而我们在哪里等着拿锁。

 常量字符串CacheKey =CacheKey;
静态只读ReaderWriterLockSlim cacheLock =新ReaderWriterLockSlim();
静态字符串GetCachedData()
{
    //首先我们做一个读锁,看它是否已经存在,这使得多个读者在同一时间。
    cacheLock.EnterReadLock();
    尝试
    {
        //如果字符串不存在返回null,prevents那里的缓存失效之间的包含检查和retreival的竞争条件。
        VAR cachedString = MemoryCache.Default.Get(CacheKey,NULL)作为字符串;        如果(cachedString!= NULL)
        {
            返回cachedString;
        }
    }
    最后
    {
        cacheLock.ExitReadLock();
    }    //只有一个UpgradeableReadLock可以在同一时间存在,但它可以与许多ReadLocks共存
    cacheLock.EnterUpgradeableReadLock();
    尝试
    {
        //我们需要再次检查,看看是否该字符串创建,而我们在这里等待进入EnterUpgradeableReadLock
        VAR cachedString = MemoryCache.Default.Get(CacheKey,NULL)作为字符串;        如果(cachedString!= NULL)
        {
            返回cachedString;
        }        //入口仍然不存在,所以我们需要输入写锁定,并创建它,这将阻止,直至所有冲洗读者。
        cacheLock.EnterWriteLock();
        尝试
        {
            CacheItemPolicy CIP =新CacheItemPolicy()
            {
                AbsoluteExpiration =新的DateTimeOffset(DateTime.Now.AddMinutes(20))
            };
            变种expensiveString = SomeHeavyAndExpensiveCalculation();
            MemoryCache.Default.Set(CacheKey,expensiveString,CIP);
            返回expensiveString;
        }
        最后
        {
            cacheLock.ExitWriteLock();
        }
    }
    最后
    {
        cacheLock.ExitUpgradeableReadLock();
    }
}

I assume this code has concurrency issues:

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

The reason for the concurrency issue is that multiple threads can get a null key and then attempt to insert data into cache.

What would be the shortest and cleanest way to make this code concurrency proof? I like to follow a good pattern across my cache related code. A link to an online article would be a great help.

UPDATE:

I came up with this code based on @Scott Chamberlain's answer. Can anyone find any performance or concurrency issue with this? If this works, it would save many line of code and errors.

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

解决方案

This is my 2nd iteration of the code. Because MemoryCache is thread safe you don't need to lock on the initial read, you can just read and if the cache returns null then do the lock check to see if you need to create the string. It greatly simplifies the code.

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


EDIT: The below code is unnecessary but I wanted to leave it to show the original method. It my be useful to future visitors who are using a different collection that has thread safe reads but non-thread safe writes (almost all of classes under the System.Collections namespace is like that).

Here is how I would do it using ReaderWriterLockSlim to protect access. You need to do a kind of "Double Checked Locking" to see if anyone else created the cached item while we where waiting to to take the lock.

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 enter the write lock and create it, this will block till all the Readers flush.
        cacheLock.EnterWriteLock();
        try
        {
            CacheItemPolicy cip = new CacheItemPolicy()
            {
                AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddMinutes(20))
            };
            var expensiveString = SomeHeavyAndExpensiveCalculation();
            MemoryCache.Default.Set(CacheKey, expensiveString, cip);
            return expensiveString;
        }
        finally 
        {
            cacheLock.ExitWriteLock();
        }
    }
    finally
    {
        cacheLock.ExitUpgradeableReadLock();
    }
}

这篇关于正确使用.NET的MemoryCache的锁定模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆