C# ConcurrentDictionary陷阱-来自GetOrAdd和AddOrUpdate的代理工厂是否同步?

C# ConcurrentDictionary陷阱-来自GetOrAdd和AddOrUpdate的代理工厂是否同步?,c#,.net,multithreading,concurrency,thread-safety,C#,.net,Multithreading,Concurrency,Thread Safety,ConcurrentDictionary的文档没有明确的状态,因此我想我们不能期望委托valueFactory和updateValueFactory的执行是同步的(分别来自GetOrAdd()和AddOrUpdate()操作) 因此,我认为如果不手动实现我们自己的并发控制,我们就无法实现对需要并发控制的资源的使用,可能只是在代理上使用[MethodImpl(methodimpoptions.Synchronized)] 我说得对吗?或者说,ConcurrentDictionary是线程安全的,我

ConcurrentDictionary
的文档没有明确的状态,因此我想我们不能期望委托
valueFactory
updateValueFactory
的执行是同步的(分别来自GetOrAdd()和AddOrUpdate()操作)

因此,我认为如果不手动实现我们自己的并发控制,我们就无法实现对需要并发控制的资源的使用,可能只是在代理上使用
[MethodImpl(methodimpoptions.Synchronized)]


我说得对吗?或者说,
ConcurrentDictionary
是线程安全的,我们可以预期对这些委托的调用是自动同步的(也是线程安全的)?

是的,你是对的,用户委托不是通过
ConcurrentDictionary
同步的。如果您需要这些同步,这是您的责任

MSDN本身说:

此外,尽管ConcurrentDictionary的所有方法都是 线程安全,不是所有方法都是原子的,特别是GetOrAdd和 添加或更新。传递给这些方法的用户委托是 在字典的内部锁之外调用。(这是为了 防止未知代码阻塞所有线程。)

这是因为
ConcurrentDictionary
不知道您提供的委托将做什么或它的性能,因此如果它试图锁定它们,可能会对性能产生负面影响,并破坏ConcurrentDictionary的价值


因此,用户有责任在必要时同步其委托。上面的MSDN链接实际上提供了一个很好的例子,说明了它所做和不做的保证。

这些委托不仅没有同步,而且甚至不能保证只发生一次。事实上,每次调用
AddOrUpdate
都可以多次执行它们

例如,
AddOrUpdate
的算法如下所示

TValue value;
do
{
  if (!TryGetValue(...))
  {
    value = addValueFactory(key);
    if (!TryAddInternal(...))
    {
      continue;
    }
    return value;
  }
  value = updateValueFactory(key);
} 
while (!TryUpdate(...))
return value;
注意这里的两件事

  • 不需要同步委托的执行
  • 委托可能会执行多次,因为它们在循环中被调用
所以你需要确保做两件事

  • 为学员提供您自己的同步
  • 确保您的代理没有任何依赖于执行次数的副作用

@Luciano:我刚刚检查了一下,
GetOrAdd
似乎只调用了一次
ValueFactory
。因此,只有
AddOrUpdate
才有可能多次调用代理。这可能是微软方面的一个缺陷……不确定。我发现这个问题很久了。@BrianGideon:当然,对不起,我没注意到你说的是AddOrUpdate而不是GetOrAdd。谢谢你的回答。到目前为止,这种行为有任何改变吗?@SebastianGodelet:我不确定。这看起来更像是一个bug,所以这种行为最终可能会得到“修复”。我理解微软的观点“不要为此呼吁我们的支持”或“安全,呆在家里”推动了这一实现。这是一个0%风险的观点。但“并发性”形容词暗示我们现在都有非工作代码!它至少可以选择加入,并在文档中显示警告。。。我很幸运,我发现了一些奇怪的事情,并挖掘它。。。否则,非工作代码将被发布给许多客户。因此,我将GetOrAdd方法调用包装在锁中,但这使得ConcurrentDictionary的用途毫无用处。有没有最好的方法?我对此有点困惑,我们基本上是说,如果委托作为键传递,它的执行将不会在计算值时同步?或者如果我们有一个类型为“delegate”的字典作为值。。。当我们尝试执行委托时(即
字典[key](参数);
),执行将不会同步?或者,我完全没有抓住要点吗?谢谢。@Snoopy:计算要添加的值的委托未同步。这意味着,如果您的委托执行的操作不是线程安全的,并且您自己没有尝试同步该操作,那么糟糕的事情就会发生。但是,作为值工厂的委托通常不会做本质上不安全的事情。大多数时候,委托可能只是在执行
new SomeObject()
,这绝对是线程安全的,因为它是一个无状态操作。@John:没有很好地解释的部分,但它仍然很有价值,因为委托可能会执行多次,只有在代理生成新值后未更改键上的值时,才会插入新值,否则-如果在代理运行时更改了值-将重试。