如何对字典中的集合值进行线程安全替换?

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

假设我有

Dictionary<string,IEnumerable<int>>
和:

Task1 尝试替换某个键下的值。

Task2 循环遍历同一 key 下的 value 元素。

这个线程安全吗?我认为如果我首先获取该值然后在 Task2 中循环它应该是这样。在这种情况下,Task2 应该获取旧值或新值,但我认为它永远不应该生成任何线程异常。

c# thread-safety
2个回答
5
投票

在讨论线程安全时,您需要“非常”精确和具体。简短的回答“不”。更长的答案“可能是,如果你努力的话,但最好重新设计一下”。 就字典而言,如果我们假设你正在改变字典:我们需要考虑 get 与 set 的竞争。这不是线程安全的,除非您对各个操作进行同步 - 如果至少有一个访问是突变,那么在同时访问字典时您“绝对”会看到异常。但坦率地说:使用

ConcurrentDictionary<,>

来代替可能更有意义。 另外,我们需要考虑序列的枚举,

IEnumerable<int>
。现在;

IEnumerable<int>

not
保证可重复,并且它在查找中的事实意味着您可能计划多次枚举它,所以:这并不理想。许多实现
IEnumerable<T>
的类型是可重复的,但这不是 API 保证。此外:其中许多类型本身是可变的,因此与这些类型的更改相比,不一定是线程安全的。
所以:如果是我,我可能会考虑使用ConcurrentDictionary<string, ImmutableArray<int>>

ConcurrentDictionary<string, ReadOnlyMemory<int>>

,然后我就有信心它是安全的...

...只要没有人使用反射或不安全代码,在这种情况下,所有的赌注都会失败!
    

我喜欢这个

线程安全

-1
投票

如果程序或方法不存在不确定性,则该程序或方法是线程安全的 面对任何多线程场景。

因此,如果您用

null

替换该值,您可以得到一个
NullReferenceException

,在单线程场景中您不会期望它(有点做作,但 ContainsKey 已经足够频繁使用)

var dict = new Dictionary<string, IEnumerable<int>>();
dict["foo"] = new List<int>() { 1, 2, 3 };
var mres = new ManualResetEventSlim(false);

Task.Run(() => {
    if (dict.ContainsKey("foo") && dict["foo"] != null) {
        mres.Wait();
        foreach (var element in dict["foo"]) {
            Console.WriteLine(element);
        }
    }
});

Thread.Sleep(100);
Task.Run(() => {
    mres.Set();
    dict["foo"] = null;
});
另一种情况是,如果您使用外部和内部 

foreach
 循环,并且外部循环对旧数据进行操作,内部循环对新数据进行操作,您希望得到的是 
same

数据。

我确信还有更多例子可以说明为什么这不是“线程安全”的。
    
	

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