当我将 `ConcurrentDictionary` 转换为 `IDictionary` 时出现奇怪的并发行为

问题描述 投票:0回答:1

我有以下测试:

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;               

public class Program
{
    public static void Main()
    {
        IDictionary<string, string> dictionary = new ConcurrentDictionary<string, string>(); // FAILS sometimes
        // ConcurrentDictionary<string, string> dictionary = new ConcurrentDictionary<string, string>(); // WORKS always

        Parallel.For(0, 10, i => dictionary.TryAdd("x1", "y1"));
    }
}

如果我在 .NET fiddle 中运行它,有时会出现以下异常:

未处理的异常。 System.AggregateException:发生一个或多个错误。 (该键已存在于字典中)
System.ArgumentException:字典中已存在该键。

在 System.Collections.Concurrent.ConcurrentDictionary

2.System.Collections.Generic.IDictionary<TKey,TValue>.Add(TKey key, TValue value)    at System.Collections.Generic.CollectionExtensions.TryAdd[TKey,TValue](IDictionary
2 字典,TKey 键,TValue 值)
在程序中。<>c__DisplayClass0_0.b__0(Int32 i)
在System.Threading.Tasks.Parallel。<>c__DisplayClass19_0
2.<ForWorker>b__1(RangeWorker& currentWorker, Int64 timeout, Boolean& replicationDelegateYieldedBeforeCompletion)   --- End of stack trace from previous location ---   at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0
2.b__1(RangeWorker&currentWorker,Int64超时,Boolean&replicationDelegateYieldedBeforeCompletion)
在 System.Threading.Tasks.TaskReplicator.Replica.Execute()
--- 内部异常堆栈跟踪结束 ---

但是,如果我不投射

ConcurrentDictionary
,它似乎总是有效。

发生什么事了?

.net concurrency thread-safety
1个回答
0
投票

ConcurrentDictionary.TryAdd 实例方法以原子方式操作。如果使用同一键同时调用此方法两次,则其中一次调用保证返回 false

(而不是抛出异常)。

通用 IDictionary 接口没有 TryAdd 实例方法。当您转换为 IDictionary 并调用 TryAdd 时,您实际上是在调用

CollectionExtensions.TryAdd 扩展方法。该方法实现如下:

if (!dictionary.ContainsKey(key)) { dictionary.Add(key, value); return true; } return false;
如您所见,此方法是

不是原子的。两个同时调用 TryAdd 都可以通过 ContainsKey 检查,然后都调用 Add,这会导致异常。

因此,您无法转换为 IDictionary 并保留 ConcurrentDictionary.TryAdd 实例方法的原子行为。

© www.soinside.com 2019 - 2024. All rights reserved.