Я припускаю, що цей код має проблеми з одночасністю:
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 Chamberlain. Чи може хтось знайти будь-яку проблему з ефективністю чи сумісністю з цим? Якщо це працює, це дозволить зберегти багато рядків коду та помилок.
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>
коли ключ - той самий ключ, який ви використовуєте у своєму, MemoryCache
а об’єкт у словнику - це лише основна, Object
яку ви замикаєте. Однак, кажучи, я рекомендую вам прочитати відповідь Джона Ханни. Без належного профілювання ви, можливо, уповільнюєте свою програму більше за допомогою блокування, ніж випускаєте два екземпляри SomeHeavyAndExpensiveCalculation()
запуску, і один результат буде викинутий.
ReaderWriterLockSlim
?