我已经开始使用一些.NET 3.5代码,并发现以下扩展方法用于缓存::
public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> @this, TKey key,Func<TKey,TValue> factory,bool useLocking)
{
TValue value;
if(!@this.TryGetValue(key,out value))
{
if (useLocking)
{
lock ((@this as ICollection).SyncRoot)
{
if (!@this.TryGetValue(key, out value))
{
@this[key] = value = factory(key);
}
}
}
else
{
@this[key] = value = factory(key);
}
}
return value;
}
Run Code Online (Sandbox Code Playgroud)
有问题的缓存由字符串键和useLocking = true键控.它总是被这种方法访问(没有流浪TryGetValue).使用该SyncRoot属性也没有问题,因为字典是私有的,没有其他地方使用它.双重锁定是危险的,因为字典在写入时不支持读取.虽然技术上没有报告任何问题,因为产品没有发货,我觉得这种方法会导致竞争条件.
切换Dictionary<,>到a Hashtable.我们将失去类型安全性,但我们将能够支持我们追求的并发模型,(1位作者,多位读者).
删除外部TryGetValue.这样每次读取都需要锁定.这可能对性能有害,但获得无争议的锁应该相当便宜.
两者都很糟糕.有人有更好的建议吗?如果这是.NET 4代码,我只需将其切换为a ConcurrentDictionary,但我没有这个选项.
删除 TryGetValue。我打赌你不会看到并发问题;CLR 监视器非常快,并且“不公平”,因此您不会看到护航或优先级反转问题。
如果您确实发现并发问题,那么下一个最好的选择是 ReaderWriterLockSlim。不幸的是,您需要为此创建一个新类,而不是使用扩展方法,因为您需要一个地方来存储锁。
如果您走这条路,请务必远离从读锁升级到写锁。