【发布时间】: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.List
1.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<T>。