【问题标题】:Locked Caching using Cache Keys使用缓存键锁定缓存
【发布时间】:2012-12-24 17:28:52
【问题描述】:

我们使用以下代码来提高性能。它工作正常,但每隔几天我们就会开始收到大量异常(如下)。它与音量无关,但它是随机的。

注释:/// 执行锁定代码以在必要时产生结果,同时线程锁定它,然后缓存结果。

第 45 行是:lock (_keys.First(k => k == key))

有什么想法吗?

代码:

    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(Boolean isPublic, String 键,CacheGetOptions getOptions) 在 PROJECT.LockedCaching.b__8(String k) 在 PROJECT\LockedCaching.cs:line 64 at System.Collections.Generic.List1.RemoveAll(Predicate1 匹配)在 PROJECT.LockedCaching.CleanUpOldKeys() 在 PROJECT\LockedCaching.cs:第 64 行 PROJECTLockedCaching.Request(字符串键,日期时间到期, RequestDataOperation 数据)在 PROJECT\LockedCaching.cs:line 58 at FeaturesWithFlags1.DataBind() 在 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp,对象 o,对象 t,EventArgs e) 在 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.Control.LoadRecursive() 在 System.Web.UI.Page.ProcessRequestMain(布尔值 includeStagesBeforeAsyncPoint,布尔型 includeStagesAfterAsyncPoint) 在 System.Web.UI.Page.HandleError(异常 e)处 System.Web.UI.Page.ProcessRequestMain(布尔值 includeStagesBeforeAsyncPoint,布尔型 includeStagesAfterAsyncPoint) 在 System.Web.UI.Page.ProcessRequest(布尔 includeStagesBeforeAsyncPoint,布尔型 includeStagesAfterAsyncPoint) 在 System.Web.UI.Page.ProcessRequest() 在 System.Web.UI.Page.ProcessRequest(HttpContext 上下文)在 System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() 在 System.Web.HttpApplication.ExecuteStep(IExecutionStep 步骤, Boolean & completedSynchronously)

使用它的 Web 控件 - 此 Web 控件从 Web 服务请求位置列表。我们几乎在调用 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();

}

感谢您的 cmets。 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>();

}

【问题讨论】:

  • 你为什么要锁定一个表达式?你认为你的代码实际上锁定第一个元素吗? lock 不是这样工作的。
  • 您的代码非常非线程安全。您应该使用 ReaderWriterLock,或者只使用一个 ConcurrentDictionary
  • 如果_keys 应该是专门用于锁定目的的同步根列表,那么_keys 在访问集合时需要自己锁定。虽然我不太确定查看您的代码的意图是什么。
  • 我不同意@JohnSaunders。这个锁确实锁定了集合中的第一个匹配元素。 at 不锁定表达式。
  • @AndrewArnott:鉴于Exists()RemoveAll(),几乎肯定是List&lt;T&gt;

标签: c# asp.net caching


【解决方案1】:

您的代码在集合中存在竞争条件。您同时写入它。这可以产生各种效果。

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

还有其他种族。您可能应该修改扩展您放在全局锁定下的代码量。注意不要使用全局锁破坏太多并发。

也许您可以切换到ConcurrentDictionary&lt;string, Lazy&lt;CacheValue&gt;&gt;。这是像您一样工作的缓存的规范模式。它不会受到缓存标记的影响。

小心穿线。在这种情况下很容易引入微妙的比赛。

【讨论】:

    【解决方案2】:

    您看到的异常表明_keys 中有一个空元素。从您的代码 sn-p 这不应该发生。所以我们看不到的其他代码要么是添加空值,要么你有线程安全问题。由于您几乎可以肯定有线程安全错误(请参阅问题 cmets),我会开始寻找那里。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-29
      • 2014-03-21
      • 1970-01-01
      • 2017-02-20
      • 2019-05-13
      • 1970-01-01
      相关资源
      最近更新 更多