2012-12-24 1 views
2

성능 향상을 위해 아래 코드를 사용합니다. 그것은 잘 작동하지만 몇 일마다 우리는 (아래) 예외 톤을 받기 시작합니다. 볼륨과 관련이 없지만 무작위입니다.캐시 키를 사용하는 잠긴 캐싱

설명 : 필요한 경우 스레드를 잠근 다음 결과를 캐싱하는 동안 잠긴 코드를 수행하여 결과를 생성합니다.

라인 (45)은 : 로크 (_keys.First (K => K == 키))

어떤 아이디어?

코드 :

public class LockedCaching 
{ 
    private static List<string> _keys = new List<string>(); 

    public class Result 
    { 
     public object Value { get; set; } 
     public bool ExecutedDataOperation { get; set; } 
    } 

    /// <summary> 
    /// Performs the locked code to produce the result if necessary while thread locking it and then caching the result. 
    /// </summary> 
    /// <param name="key"></param> 
    /// <param name="expiration"></param> 
    /// <param name="data"></param> 
    /// <returns></returns> 
    public static Result Request(string key, DateTime expiration, RequestDataOperation data) 
    { 
     if (key == null) 
     { 
      return new Result { Value = data(), ExecutedDataOperation = true }; 
     } 

     //Does the key have an instance for locking yet (in our _keys list)? 
     bool addedKey = false; 
     bool executedDataOperation = false; 
     if (!_keys.Exists(s => s == key)) 
     { 
      _keys.Add(key); 
      addedKey = true; 
     } 
     object ret = HttpContext.Current.Cache[key]; 
     if (ret == null) 
     { 
      lock (_keys.First(k => k == key)) 
      { 
       ret = HttpContext.Current.Cache[key]; 
       if (ret == null) 
       { 
        ret = data(); 
        executedDataOperation = true; 
        if(ret != null) 
         HttpContext.Current.Cache.Insert(key, ret, null, expiration, new TimeSpan(0)); 
       } 
      } 
     } 
     if (addedKey) 
      CleanUpOldKeys(); 
     return new Result { Value = ret, ExecutedDataOperation = executedDataOperation }; 
    } 

    private static void CleanUpOldKeys() 
    { 
     _keys.RemoveAll(k => HttpContext.Current.Cache[k] == null); 
    } 
} 

예외 :

예외 : System.Web.HttpUnhandledException (0X80004005) 형식 'System.Web.HttpUnhandledException'의 는 던져졌다 예외입니다. ---> System.ArgumentNullException : 값은 null 일 수 없습니다. 매개 변수 이름 : 프로젝트 \의 LockedCaching.cs에서 PROJECT.LockedCaching.b__8에서 System.Web.Caching.CacheInternal.DoGet (부울 isPublic, 문자열 키 CacheGetOptions의 getOptions() 문자열 K)에서 키 : 라인 64 에서 시스템. 라인 64 PROJECT \ LockedCaching.cs에서 PROJECTLockedCaching.Request (문자열 키, 날짜 시간 만료, RequestDataOperation 데이터)에서 : 프로젝트 \의 LockedCaching.cs에서 PROJECT.LockedCaching.CleanUpOldKeys()에서 Collections.Generic.List 1.RemoveAll(Predicate 1 경기) : 줄 58에서 FeaturesWithFlags1.DataBind() 에서 System.Web.UI.Control.LoadRecursive()에서 System.Web.Util.CalliHelper.EventArgFunctionCaller (IntPtr fp, Object , Object t, EventArgs e) t 에서 System.Web.UI.Control.LoadRecursive() 에서 System.Web.UI.Control.LoadRecursive() 에서 System.Web.UI.Control.LoadRecursive() System.Web.UI.Control.LoadRecursive()에 System.Web.UI.Page.HandleError에서 System.Web.UI.Page.ProcessRequestMain (부울 includeStagesBeforeAsyncPoint 부울 includeStagesAfterAsyncPoint) 에서 System.Web.UI.Control.LoadRecursive() (예외 E)에서 System.Web.UI.Page.ProcessRequestMain System.Web.UI.Page.ProcessRequest에서 (부울 includeStagesBeforeAsyncPoint 부울 includeStagesAfterAsyncPoint) (부울 includeStagesBeforeAsyncPoint 부울 includeStagesAfterAsyncPoint),745 System.Web.UI.Page.ProcessRequest()의의 System.Web.UI.Page.ProcessRequest (HttpContext context) System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() 이 웹 제어를 요청한다 웹 서비스에서 위치 목록 - System.Web.HttpApplication.ExecuteStep 때 (IExecutionStep 단계, 부울 & completedSynchronously)이 사용되는

웹 컨트롤에서. 우리는 우리가 웹 서비스 :

public override void DataBind() 
{ 
    try 
    { 
     string cacheKey = "GetSites|"; 
     mt_site_config[] sites = (mt_site_config[])LockedCaching.Request(cacheKey, DateTime.UtcNow.AddMinutes(10), 
     () => 
     { 
      WebServiceClient service = new WebServiceClient(); 
      sites = service.GetSites(); 
      service.Close(); 
      return sites; 
     }).Value; 
     ddlLocation.Items.Clear(); 
     ddlLocation.Items.Add(new ListItem("Please Select")); 
     ddlLocation.Items.Add(new ListItem("Administration")); 
     ddlLocation.Items.AddRange 
     (
      sites.Select 
      (
       s => new ListItem(s.site_name + " " + s.site_location, s.th_code.ToString()) 
      ).ToArray() 
     ); 
    } 
    catch (Exception ex) { 
     Logger.Error("ContactUs Control Exception: Exp" + Environment.NewLine + ex.Message); 
    } 
    base.DataBind(); 

}

는 귀하의 의견 주셔서 감사 전화를 거의 모든 곳이 lockedcache 요청을 사용합니다. ConcurrentDictionary가 방법이었습니다. 우리가 오류를받은 이유는 linq 코드 "lock (_keys.First (k => k == key))"이 null이 아닌 예외를 반환했기 때문입니다.concurrentdictionary를 사용하면 훨씬 안전 해지고 잘하면 잠금 문제가 발생하지 않습니다.

수정 된 코드 :

public class LockedCaching 
{ 

    public class Result 
    { 
     public object Value { get; set; } 
     public bool ExecutedDataOperation { get; set; } 
    } 

    public static Result Request(string key, DateTime expiration, RequestDataOperation data) 
    { 
     if (key == null) 
     { 
      return new Result { Value = data(), ExecutedDataOperation = true }; 
     } 

     object results = HttpContext.Current.Cache[key]; 
     bool executedDataOperation = false; 

     if (results == null) 
     { 
      object miniLock = _miniLocks.GetOrAdd(key, k => new object()); 
      lock (miniLock) 
      { 
       results = HttpContext.Current.Cache[key]; 
       if (results == null) 
       { 
        results = data(); 
        executedDataOperation = true; 
        if (results != null) 
         HttpContext.Current.Cache.Insert(key, results, null, expiration, new TimeSpan(0)); 

        object temp; 
        object tempResults; 
        if (_miniLocks.TryGetValue(key, out temp) && (temp == miniLock)) 
         _miniLocks.TryRemove(key, out tempResults); 

       } 
      } 
     } 
     return new Result { Value = results, ExecutedDataOperation = executedDataOperation }; 
    } 

    private static readonly ConcurrentDictionary<string, object> _miniLocks = 
           new ConcurrentDictionary<string, object>(); 

} 
+0

왜 식을 잠그려고합니까? 코드가 실제로 첫 번째 요소 인 _locks_이라고 생각합니까? 그건'자물쇠'가 작동하는 방식이 아닙니다. –

+0

코드가 스레드로부터 안전하지 않습니다. ReaderWriterLock을 사용하거나 하나의 'ConcurrentDictionary'만 사용해야합니다. – SLaks

+1

'_keys'가 잠금 목적으로 특별히 사용 된 syncroots의리스트라고 가정하면,'_keys'는 콜렉션에 접근하는 동안 그 자체가 잠길 필요가 있습니다. 비록 의도가 당신의 코드를 어떻게 바라보고 있는지 잘 모르겠지만. –

답변

3

코드의 컬렉션에 경쟁 조건이 있습니다. 당신은 동시에 그것에 쓸 수 있습니다. 이것은 모든 종류의 효과를 가질 수 있습니다.

다른 종족도 있습니다. 전역 잠금 아래에 두는 코드의 양을 수정해야합니다. 해당 전역 잠금을 사용하여 너무 많은 동시성을 파괴하지 않도록주의하십시오.

아마도 ConcurrentDictionary<string, Lazy<CacheValue>>으로 전환 할 수 있습니다. 이것은 당신처럼 작동하는 캐시의 정식 패턴입니다. 캐시 우표가 없어지지 않습니다.

스레딩에주의하십시오. 이 경우와 같이 미묘한 인종을 소개하는 것은 쉽습니다.

0

당신이보고있는 예외는 _keys 그것에 널 요소가 있음을 나타냅니다. 코드 스 니펫에서 이런 일이 발생하지 않아야합니다. 따라서 우리가 볼 수없는 다른 코드는 null을 추가하거나 스레드 안전성 문제가 있습니다. 거의 확실하게 스레드 안전 버그가 있기 때문에 (질문 주석 참조) 나는 거기에서부터 살펴보기 시작할 것입니다.