ConcurrentDictionary TryAdd与Item setter性能

我们在一个高流量的网站上,我们想要使用ConcurrentDictionary进行一些非常简单的缓存计算,以防止它为每个请求完成。 可能的输入数量足够有限,计算相对较重(拆分和重新连接字符串)。

我正在考虑代码

string result; if (!MyConCurrentDictionary.TryGetValue(someKey, out result)) { result = DoCalculation(someKey); // alternative 1: use Item property MyConcurrentDictionary[someKey] = result; //alternative 2: use TryAdd MyConcurrentDictionary.TryAdd(someKey, result); } 

我的问题是:从绩效角度来看,哪种替代方案是最佳选择?

正如@SLaks所示,您的代码存在竞争条件。 构建ConcurrentDictionary是为了通过提供以primefaces方式执行复杂操作的方法(例如GetOrAddAddOrUpdate来防止此类情况。

这是一个描述代码如何破坏的序列:

  • 线程1执行TryGetValue ,返回false
  • 线程2也是如此
  • 两个线程都进行计算
  • 线程2将值添加到字典并返回结果
  • 线程1也是
    • (使用索引器设置器)添加另一个值,覆盖前一个值并返回它
    • (使用TryAdd )不会添加它,并且该方法返回的结果可能与字典中的结果不同
  • 线程2现在的结果可能与字典中的结果不同

因此,您可以看到如何不使用GetOrAdd使事情变得更复杂,并可能产生潜在的灾难性后果。 在你的情况下,如果你确定性地从键中计算一个字符串,那么这可能无关紧要 – 但实际上没有理由不使用这种方法。 它还简化了代码,并且可以(略微)提高性能,因为它只计算一次哈希码。

另一个结论是在索引器和TryAdd之间进行选择更多的是正确性问题而不是性能问题。

也就是说,indexer []TryAdd方法的性能是相同的,因为两者共享相同的实现( TryAddInternal ),它通过获取属于密钥桶的锁,然后检查密钥是否存在于字典中,以及是否更新字典。

最后,这是一个示例 ,说明如何正确构建像GetOrAdd这样的方法。

你的代码完全坏了; 你假设这两行之间什么都不会改变。

你需要使用.GetOrAdd()

通常,在处理可变并发对象时,必须永远不要对该对象进行多次调用,因为它的状态可以随时改变。