장기 실행 프로세스와 같은 특정 상황에서는 해당 리소스에 대한 다른 사용자의 후속 요청이 캐시에 도달하는 대신 장기 프로세스를 다시 실행하지 못하도록 ASP.NET 캐시를 잠그는 것이 중요하다는 것을 알고 있습니다.
ASP.NET에서 캐시 잠금을 구현하는 C #의 가장 좋은 방법은 무엇입니까?
답변
다음은 기본 패턴입니다.
- 캐시에서 값을 확인하고 사용 가능한 경우 반환하십시오.
- 값이 캐시에 없으면 잠금을 구현하십시오.
- 잠금 장치 내부에서 캐시를 다시 확인하십시오. 차단되었을 수 있습니다.
- 값 조회를 수행하고 캐시합니다.
- 잠금 해제
코드에서는 다음과 같습니다.
private static object ThisLock = new object();
public string GetFoo()
{
// try to pull from cache here
lock (ThisLock)
{
// cache was empty before we got the lock, check again inside the lock
// cache is still empty, so retreive the value here
// store the value in the cache here
}
// return the cached value here
}
답변
완전성을 위해 전체 예제는 다음과 같습니다.
private static object ThisLock = new object();
...
object dataObject = Cache["globalData"];
if( dataObject == null )
{
lock( ThisLock )
{
dataObject = Cache["globalData"];
if( dataObject == null )
{
//Get Data from db
dataObject = GlobalObj.GetData();
Cache["globalData"] = dataObject;
}
}
}
return dataObject;
답변
전체 캐시 인스턴스를 잠글 필요가 없으며 삽입하려는 특정 키만 잠그면됩니다. 즉, 남성 화장실 이용시 여성 화장실 출입을 차단할 필요가 없습니다. 🙂
아래 구현에서는 동시 사전을 사용하여 특정 캐시 키를 잠글 수 있습니다. 이렇게하면 동시에 두 개의 다른 키에 대해 GetOrAdd ()를 실행할 수 있지만 동시에 같은 키에 대해서는 실행할 수 없습니다.
using System;
using System.Collections.Concurrent;
using System.Web.Caching;
public static class CacheExtensions
{
private static ConcurrentDictionary<string, object> keyLocks = new ConcurrentDictionary<string, object>();
/// <summary>
/// Get or Add the item to the cache using the given key. Lazily executes the value factory only if/when needed
/// </summary>
public static T GetOrAdd<T>(this Cache cache, string key, int durationInSeconds, Func<T> factory)
where T : class
{
// Try and get value from the cache
var value = cache.Get(key);
if (value == null)
{
// If not yet cached, lock the key value and add to cache
lock (keyLocks.GetOrAdd(key, new object()))
{
// Try and get from cache again in case it has been added in the meantime
value = cache.Get(key);
if (value == null && (value = factory()) != null)
{
// TODO: Some of these parameters could be added to method signature later if required
cache.Insert(
key: key,
value: value,
dependencies: null,
absoluteExpiration: DateTime.Now.AddSeconds(durationInSeconds),
slidingExpiration: Cache.NoSlidingExpiration,
priority: CacheItemPriority.Default,
onRemoveCallback: null);
}
// Remove temporary key lock
keyLocks.TryRemove(key, out object locker);
}
}
return value as T;
}
}
답변
Pavel이 말한 것을 반영하기 위해 이것이 가장 스레드로부터 안전한 작성 방법이라고 생각합니다.
private T GetOrAddToCache<T>(string cacheKey, GenericObjectParamsDelegate<T> creator, params object[] creatorArgs) where T : class, new()
{
T returnValue = HttpContext.Current.Cache[cacheKey] as T;
if (returnValue == null)
{
lock (this)
{
returnValue = HttpContext.Current.Cache[cacheKey] as T;
if (returnValue == null)
{
returnValue = creator(creatorArgs);
if (returnValue == null)
{
throw new Exception("Attempt to cache a null reference");
}
HttpContext.Current.Cache.Add(
cacheKey,
returnValue,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
System.Web.Caching.Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null);
}
}
}
return returnValue;
}
답변
Craig Shoemaker는 asp.net 캐싱에 대한 훌륭한 쇼를 만들었습니다 :
http://polymorphicpodcast.com/shows/webperformance/
답변
다음과 같은 확장 방법을 생각해 냈습니다.
private static readonly object _lock = new object();
public static TResult GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action, int duration = 300) {
TResult result;
var data = cache[key]; // Can't cast using as operator as TResult may be an int or bool
if (data == null) {
lock (_lock) {
data = cache[key];
if (data == null) {
result = action();
if (result == null)
return result;
if (duration > 0)
cache.Insert(key, result, null, DateTime.UtcNow.AddSeconds(duration), TimeSpan.Zero);
} else
result = (TResult)data;
}
} else
result = (TResult)data;
return result;
}
@John Owen과 @ user378380 답변을 모두 사용했습니다. 내 솔루션을 사용하면 캐시 내에 int 및 bool 값을 저장할 수도 있습니다.
오류가 있거나 조금 더 잘 쓸 수 있는지 정정하십시오.
답변
최근에 올바른 State Bag Access Pattern이라는 패턴을 보았습니다.
스레드로부터 안전하도록 약간 수정했습니다.
http://weblogs.asp.net/craigshoemaker/archive/2008/08/28/asp-net-caching-and-performance.aspx
private static object _listLock = new object();
public List List() {
string cacheKey = "customers";
List myList = Cache[cacheKey] as List;
if(myList == null) {
lock (_listLock) {
myList = Cache[cacheKey] as List;
if (myList == null) {
myList = DAL.ListCustomers();
Cache.Insert(cacheKey, mList, null, SiteConfig.CacheDuration, TimeSpan.Zero);
}
}
}
return myList;
}