Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/331.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# ConcurrentDictionary键或值属性是线程安全的吗_C#_.net_Multithreading - Fatal编程技术网

C# ConcurrentDictionary键或值属性是线程安全的吗

C# ConcurrentDictionary键或值属性是线程安全的吗,c#,.net,multithreading,C#,.net,Multithreading,对使用ConcurrentDictionary的线程安全性有疑问。从API中,我看到枚举器是线程安全的,但我没有看到键和值属性是线程安全的。我的问题是: 当有其他线程同时修改集合时,在键或值集合上循环是否安全 ConcurrentDictionary表示线程安全的键值集合 可由多个线程同时访问的对 来源:虽然我确实喜欢这些文档,但当有疑问或者我觉得我可能假设得太多时,我倾向于用一个小程序来验证 下面的代码验证您确实可以安全地枚举values集合,同时将键从单独的线程添加或删除到正在进行枚举的线程

对使用
ConcurrentDictionary
的线程安全性有疑问。从API中,我看到枚举器是线程安全的,但我没有看到键和值属性是线程安全的。我的问题是:

当有其他线程同时修改集合时,在
集合上循环是否安全

ConcurrentDictionary表示线程安全的键值集合 可由多个线程同时访问的对


来源:

虽然我确实喜欢这些文档,但当有疑问或者我觉得我可能假设得太多时,我倾向于用一个小程序来验证

下面的代码验证您确实可以安全地枚举values集合,同时将键从单独的线程添加或删除到正在进行枚举的线程。这不会导致通常的集合被修改异常。更详细地说,这里有几个测试用例

案例1:枚举值并删除键

如果按照以下顺序操作:

  • 开始枚举线程中的值集合
  • 从我们尚未枚举的其他线程中删除密钥
  • 继续在原始线程上枚举
观察到的行为是,删除的键确实将被枚举,因为在我们开始枚举时,它存在于值集合中。不会提出任何例外

案例2:枚举值并添加键

  • 开始枚举线程中的值集合
  • 从我们尚未枚举的另一个线程添加一个新密钥
  • 继续在原始线程上枚举
观察到的行为是不会枚举添加的键,因为在我们开始枚举它时,它不存在于值集合中。无论我们使用TryAdd还是通过直接分配给字典(即dictionary[key]=value)来添加,都不会引发异常

示例代码

下面是演示这两种情况的示例程序:

ConcurrentDictionary<int, int> dictionary = new ConcurrentDictionary<int, int>();

// Seed the dictionary with some arbitrary values; 
for (int i = 0; i < 30; i++)
{
    dictionary.TryAdd(i, i);
}

// Reader thread - Enumerate the Values collection
Task.Factory.StartNew(
        () =>
        {
            foreach (var item in dictionary.Values)
            {
                Console.WriteLine("Item {0}: count: {1}", item, dictionary.Count);
                Thread.Sleep(20);
            }

        }
);

// writer thread - Modify dictionary by adding new items and removing existing ones from the end
Task.Factory.StartNew(
        () =>
        {
            for (int i = 29; i >= 0; i--)
            {
                Thread.Sleep(10);
                //Remove an existing entry 
                int removedValue;
                if (dictionary.TryRemove(i, out removedValue))
                    Console.WriteLine("Removed item {0}", removedValue);
                else
                    Console.WriteLine("Did not remove item {0}", i);

                int iVal = 50 + i*2;
                dictionary[iVal] = iVal;
                Thread.Sleep(10);
                iVal++;
                dictionary.TryAdd(iVal, iVal);
            }
        }
);

Console.ReadKey();
ConcurrentDictionary=新建ConcurrentDictionary();
//为字典添加一些任意值;
对于(int i=0;i<30;i++)
{
字典.TryAdd(i,i);
}
//读卡器线程-枚举值集合
Task.Factory.StartNew(
() =>
{
foreach(dictionary.Values中的变量项)
{
WriteLine(“项{0}:count:{1}”,项,dictionary.count);
睡眠(20);
}
}
);
//编写器线程-通过添加新项并从末尾删除现有项来修改字典
Task.Factory.StartNew(
() =>
{
对于(int i=29;i>=0;i--)
{
睡眠(10);
//删除现有条目
内移值;
if(dictionary.TryRemove(i,out removedValue))
WriteLine(“删除的项{0}”,removedValue);
其他的
WriteLine(“未删除项{0}”,i);
int-iVal=50+i*2;
字典[iVal]=iVal;
睡眠(10);
iVal++;
字典.TryAdd(iVal,iVal);
}
}
);
Console.ReadKey();
这是释放模式下的输出:

是的,它是线程安全的。但是,即使它是线程安全的,您也不希望使用
,以及
计数

尤其是当您使用
ConcurrentCollections
时,因为您希望最小化锁争用、线程阻塞和内存分配。如果你关心性能和效率,你确实想要这些东西

查看以了解为什么-
Keys
会立即调用
GetKeys()
帮助程序,并在继续之前获取每个锁。一旦有了锁,它会将每个密钥复制到一个新列表中,并返回该列表的只读视图,这样就不会有人意外地改变密钥集合的临时副本。这需要分配相当大的数组,如果您的集合很大,则需要在相当长的一段时间内保持锁

以类似于
键的方式锁定和复制每个值
。即使是
Count
也会获取所有锁,不是为了复制,而是为了汇总所有内部表段长度。所有这些都只是为了获得集合中对象的瞬时“一致”计数,这仅在锁释放后用作粗略估计或历史脚注

是的,叹息,如果你需要原子一致性,我想这可能是你必须付出的代价。但是也许你比那更幸运。然后,您可能会意识到,您的场景实际上并不需要一致性,而对于那些糟糕的API,性能更高的API就在您的掌握之中——比如使用
GetEnumerator()
来大致了解集合中的项目!
GetEnumerator()
文档中的备注:

从字典返回的枚举数可以安全使用 同时读取和写入字典,但是 不代表字典的即时快照。这个 通过枚举器公开的内容可能包含所做的修改 在调用GetEnumerator后将其添加到字典

换句话说,它根本不需要锁定或复制,因为它不需要确保一致性。万岁

+1,简明!:)添加:是的,这两个属性都受锁保护,您将枚举的是启动枚举时字典的内容(因此您可能会看到一个键,例如,它已从另一个线程中删除)。因此您需要