看这么几句解释(英文原帖):
private static void ConcurrentDictionary() { var dict = new ConcurrentDictionary<int, string>(); ThreadPool.QueueUserWorkItem(LongGetOrAdd(dict, 1)); ThreadPool.QueueUserWorkItem(LongGetOrAdd(dict, 1)); } private static WaitCallback LongGetOrAdd(ConcurrentDictionary<int, string> dict, int index) { return o => dict.GetOrAdd(index,i => { Console.WriteLine("Adding!"); Thread.SpinWait(1000); return i.ToString(); } ); }
1) threadA calls GetOrAdd, finds no item and creates a new item to Add by invoking the valueFactory delegate.
线程A调用GetOrAdd,发现数据不存在并创建了一条新数据,准备调用委托方法添加数据
2) threadB calls GetOrAdd concurrently, its valueFactory delegate is invoked and it arrives at the internal lock before threadA, and so its new key-value pair is added to the dictionary.
线程B现在也在调用GetOrAdd方法,它的委托被调用了,比线程A先进入GetOrAdd内部的锁,因此它创建的键值对被添加到dict中了
3) threadA’s user delegate completes, and the thread arrives at the lock, but now sees that the item exists already
线A的委托执行完成了,并且也获得了GetOrAdd的锁,但是现在发现相关数据已经存在了
4) threadA performs a "Get", and returns the data th at was previously added by threadB.
线程A执行“Get”,返回之前被线程B添加的数据
不止这个英文原帖,好多国内的帖子都在说这个问题。
然而我觉得这并不是个问题,这最多是一个编程时需要注意的事项,微软的这个设计是合理的:ConcurrentDictionary没有理由也没有职责去把外部代码加锁,外部代码的同步应该由
外部控制。回头看看上面的解释,我们发现只不过是传递给ConcurrentDictionary的委托被执行了不止一次,但数据仍然只添加了一次,ConcurrentDictionary已经完全履行了自己的职责,而且MSDN也作了相关的说明(事实上,上面的四句英文也来自MSDN):
The user delegate that is passed to these methods is invoked outside of the dictionary‘s internal lock. (This is done to prevent unknown code from blocking all threads.)
传递进来的用户委托在字典内部锁的外面调用,这么做是为了防止阻塞所有相关线程,具体参考MSDN。