Zakładam, że ten kod ma problemy ze współbieżnością:
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;
}
Przyczyną problemu ze współbieżnością jest to, że wiele wątków może uzyskać klucz o wartości null, a następnie próbować wstawić dane do pamięci podręcznej.
Jaki byłby najkrótszy i najczystszy sposób na udowodnienie współbieżności tego kodu? Lubię podążać za dobrym wzorcem w moim kodzie związanym z pamięcią podręczną. Bardzo pomocny byłby link do artykułu online.
AKTUALIZACJA:
Wymyśliłem ten kod na podstawie odpowiedzi @Scott Chamberlain. Czy ktoś może znaleźć w tym problem wydajności lub współbieżności? Jeśli to zadziała, zaoszczędziłoby to wiele linii kodu i błędów.
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>
jeśli klucz jest tym samym kluczem, którego używasz w swoim, MemoryCache
a obiekt w słowniku to tylko podstawowy, Object
który blokujesz. Jednak biorąc to pod uwagę, polecam przeczytanie odpowiedzi Jona Hanny. Bez odpowiedniego profilowania możesz bardziej spowalniać program blokując niż pozwalając na dwie instancje SomeHeavyAndExpensiveCalculation()
uruchomienia i mieć jeden wynik odrzucony.
ReaderWriterLockSlim
?