2012-12-24 69 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:值不能爲空。參數名稱:在System.Web.Caching.CacheInternal.DoGet(布爾isPublic,字符串 鍵,CacheGetOptions getOptions)在PROJECT.LockedCaching.b__8(字符串 K)在PROJECT \ LockedCaching.cs 鍵:64在 線系統。 Collections.Generic.List 1.RemoveAll(Predicate 1匹配)在 PROJECT.LockedCaching.CleanUpOldKeys()在 PROJECT \ LockedCaching.cs:線64在PROJECT \ LockedCaching.cs PROJECTLockedCaching.Request(String鍵,日期時間期滿時, RequestDataOperation數據) :line35 at FeaturesWithFlags1.DataBind()at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp,Object o,Object t,EventArgs e)at System.Web.UI.Control.LoadRecursive()a噸 System.Web.UI.Control.LoadRecursive()處 System.Web.UI.Control.LoadRecursive() System.Web.UI.Control.LoadRecursive()在 System.Web.UI.Control.LoadRecursive ()在 System.Web.UI.Control.LoadRecursive()在 System.Web.UI.Page.ProcessRequestMain(布爾 includeStagesBeforeAsyncPoint,布爾includeStagesAfterAsyncPoint) 在System.Web.UI.Page.HandleError(例外五)在 System.Web.UI.Page.ProcessRequestMain(布爾 includeStagesBeforeAsyncPoint,布爾includeStagesAfterAsyncPoint) 在System.Web.UI.Page.ProcessRequest(布爾 includeStagesBeforeAsyncPoint,布爾includeStagesAfterAsyncPoint)System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() System.Web.UI.Page.ProcessRequest(HttpContext上下文)在System.Web.UI.Page.ProcessRequest()在System.Web.HttpApplication.ExecuteStep(IExecutionStep一步, 布爾& completedSynchronously)使用它的

Web控件 - 這一網絡控制請求,從web服務位置的列表。我們使用這個lockedcache要求幾乎無處不在,我們稱之爲Web服務:

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(); 

}

謝謝您的意見。 ConcurrentDictionary是要走的路。我們收到錯誤的原因是因爲linq代碼「lock(_keys.First(k => k == key))」返回一個異常而不是null。使用並行字典將會更安全,並且不會導致任何鎖定問題。

修改代碼:

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_的第一個元素嗎?這不是'lock'的工作方式。 –

+0

你的代碼是非線程安全的。您應該使用ReaderWriterLock,或者只使用一個'ConcurrentDictionary'。 – SLaks

+1

如果'_keys'應該是專門用於鎖定目的的同步列表,那麼在訪問集合時'_keys'本身必須被鎖定。雖然我不確定你的代碼的目的是什麼。 –

回答

3

您的代碼在集合上存在爭用條件。你同時寫信給它。這可以產生各種效果。

_keys.Add(key); 
... 
_keys.RemoveAll(k => HttpContext.Current.Cache[k] == null); 

還有其他種族。您可能應該修改擴展您放在全局鎖定下的代碼量。注意不要通過使用該全局鎖銷燬太多的併發。你可以切換到ConcurrentDictionary<string, Lazy<CacheValue>>。這是一個像你的一樣工作的緩存的規範模式。它不會受到緩存的困擾。

小心線程。在這種情況下很容易引入微妙的比賽。

0

你看到的異常指示_keys中有一個空元素。從你的代碼片段中,這不應該發生。因此,我們無法看到的其他代碼是添加空值,或者您有線程安全問題。既然你幾乎肯定有線程安全的bug(請參閱問題評論),我會開始在那裏看。