Je suppose que ce code a des problèmes de concurrence:
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;
}
Le problème de concurrence est dû au fait que plusieurs threads peuvent obtenir une clé nulle, puis tenter d'insérer des données dans le cache.
Quel serait le moyen le plus court et le plus propre de rendre ce code à l'épreuve de la concurrence? J'aime suivre un bon modèle dans mon code lié au cache. Un lien vers un article en ligne serait d'une grande aide.
METTRE À JOUR:
J'ai trouvé ce code basé sur la réponse de @Scott Chamberlain. Quelqu'un peut-il trouver un problème de performance ou de concurrence avec cela? Si cela fonctionne, cela sauverait de nombreuses lignes de code et d'erreurs.
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;
}
}
}
}
}
Dictionary<string, object>
où la clé est la même clé que vous utilisez dans votre MemoryCache
et l'objet dans le dictionnaire est juste un élément de base Object
sur lequel vous verrouillez. Cependant, cela étant dit, je vous recommanderais de lire la réponse de Jon Hanna. Sans un profilage approprié, vous risquez de ralentir davantage votre programme avec le verrouillage que de louer deux instances d' SomeHeavyAndExpensiveCalculation()
exécution et de perdre un résultat.
ReaderWriterLockSlim
?