对于.NET 3.5,如何创建线程安全的get或add缓存?

Mic*_*l B 8 c# concurrency

我已经开始使用一些.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属性也没有问题,因为字典是私有的,没有其他地方使用它.双重锁定是危险的,因为字典在写入时不支持读取.虽然技术上没有报告任何问题,因为产品没有发货,我觉得这种方法会导致竞争条件.

  1. 切换Dictionary<,>到a Hashtable.我们将失去类型安全性,但我们将能够支持我们追求的并发模型,(1位作者,多位读者).

  2. 删除外部TryGetValue.这样每次读取都需要锁定.这可能对性能有害,但获得无争议的锁应该相当便宜.

两者都很糟糕.有人有更好的建议吗?如果这是.NET 4代码,我只需将其切换为a ConcurrentDictionary,但我没有这个选项.

Joh*_*oty 0

删除 TryGetValue。我打赌你不会看到并发问题;CLR 监视器非常快,并且“不公平”,因此您不会看到护航或优先级反转问题。

如果您确实发现并发问题,那么下一个最好的选择是 ReaderWriterLockSlim。不幸的是,您需要为此创建一个新类,而不是使用扩展方法,因为您需要一个地方来存储锁。

如果您走这条路,请务必远离从读锁升级到写锁。