在使用Linq获取这些值后,是否可以安全地访问ConcurrentDictionary值

时间:2018-01-08 11:10:00

标签: c# multithreading linq thread-safety concurrentdictionary

我有一个像这样的ConcurrentDictionary:

ConcurrentDictionary<int, Dto> concurrentDictionary = new ConcurrentDictionary<int, Dto>();

这是可读写的字典,可供许多线程使用。我可以以线程安全的方式管理可写端。当我需要在字典中访问Dto时,我总是使用Linq select方法来获取Dto的。

IEnumerable<Dto> dtos = concurrentDictionary.Select(p => p.Value);

我知道,并发字典上的Linq方法对于可读写的并发字典是无锁和线程安全的。在使用Linq访问Dto之后,使用这些Dto上的一些读取函数是否可以安全线程?我的意思是在foreach循环中访问这些Dto,或者从它们创建新列表并对这些Dto的属性进行过滤或排序?

ConcurrentDictionary<int, Dto> concurrentDictionary = new ConcurrentDictionary<int, Dto>();
for (int i = 0; i < 10000; i++)
{
    concurrentDictionary.TryAdd(i, new Dto()
    { Id = i, Name = RandomString(35), Type = "new", IsActive = true} );
}

IEnumerable<Dto> dtos = concurrentDictionary.Select(p => p.Value);

ArrayList arrayList = new ArrayList();
foreach (object obj in dtos)
{
    //is it thread safe accessing the Dto like this and adding them to new arraylist?
    arrayList.Add(obj);
}

//Is it thread safe accessing the Dto's properties like this? 
//Of course only for reading purposes.
string name = ((Dto) arrayList[0]).Name;

1 个答案:

答案 0 :(得分:1)

根据GetEnumerator ConcurrentDictionary的文档(突出显示是我的):

  

从字典返回的枚举器可以安全使用   同时读取和写入字典,但确实如此   不代表字典的即时快照。该   通过枚举器公开的内容可能包含所做的修改   在调用GetEnumerator之后到字典。

您是否认为此线程安全取决于您需要的语义。请注意,如果您执行想要快照,则可以通过调用ToArray()方法来实现此目的:

IEnumerable<Dto> dtos = concurrentDictionary.ToArray().Select(p => p.Value);

根据documentationToArray()返回:

  

包含从中复制的键和值对的快照的新数组   System.Collections.Concurrent.ConcurrentDictionary。

(请注意,这是属于ToArray的{​​{1}}方法,而不是Linq ConcurrentDictionary方法,它将使用枚举器。)

最后,请注意,正如已经指出的那样,如果你的DTO是可变的,那么所有的赌注都会被取消。您可能会获得列表的快照,但没有什么可以防止该列表的内容在另一个线程上被更改。

修改

通过阅读评论中链接到的page,接口点是:

  

ConcurrentDictionary的所有公共成员和受保护成员   是线程安全的,可以从多个线程同时使用。   但是,成员通过其中一个接口访问了   ConcurrentDictionary实现,包括扩展   方法,不保证是线程安全的,可能需要   由来电者同步。

这告诉我们,当与ToArray()一起使用时,Linq不保证是线程安全的。 ConcurrentDictionary应该没问题,因为这会调用for each,其中包含本帖开头提到的保证。我的预感是GetEnumerator也没关系,但不能保证。{/ p>

所以回答代码中的问题:

Select

可能,但不保证,无论如何,这对你没有任何好处IEnumerable<Dto> dtos = concurrentDictionary.Select(p => p.Value); ArrayList arrayList = new ArrayList(); foreach (object obj in dtos) { //is it thread safe accessing the Dto like this and adding them to new arraylist? arrayList.Add(obj); } ,所以我会用以下内容替换所有内容:

ToArray()

其次:

KeyValuePair<int, Dto>[] dtoArray = concurrentDictionary.ToArray();

这是否安全与//Is it thread safe accessing the Dto's properties like this? //Of course only for reading purposes. string name = ((Dto) arrayList[0]).Name; 无关,而与ConcurrentDictionaryName方法的实施有关。是的,您可以安全地访问Dto,但您访问的任何属性或方法都需要是线程安全的。如果您使用了上面建议的Dto,那么

ToArray()

是线程安全的,但是

Dto dto = dtoArray[0].Value

取决于string name = dtoArray[0].Value.Name 的实施。