使用 Linq 获取这些值后访问 ConcurrentDictionary 值是否线程安全

rod*_*ins 4 c# linq multithreading thread-safety concurrentdictionary

我有一个像这样的 ConcurrentDictionary:

ConcurrentDictionary<int, Dto> concurrentDictionary = new ConcurrentDictionary<int, Dto>();
Run Code Online (Sandbox Code Playgroud)

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

IEnumerable<Dto> dtos = concurrentDictionary.Select(p => p.Value);
Run Code Online (Sandbox Code Playgroud)

我知道,并发字典上的 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;
Run Code Online (Sandbox Code Playgroud)

bor*_*egg 5

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

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

你是否考虑这个线程安全取决于你需要什么语义。请注意,如果您确实需要快照,则可以通过调用该ToArray()方法来获取:

IEnumerable<Dto> dtos = concurrentDictionary.ToArray().Select(p => p.Value);
Run Code Online (Sandbox Code Playgroud)

根据文档ToArray()返回:

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

(请注意,这是ToArray属于ConcurrentDictionaryToArray()方法,而不是将使用枚举器的 Linq方法。)

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

编辑:

通过阅读您在评论中链接到的页面,要点是:

ConcurrentDictionary 的所有公共成员和受保护成员都是线程安全的,可以从多个线程同时使用。但是,通过 ConcurrentDictionary 实现的接口之一(包括扩展方法)访问的成员不能保证是线程安全的,并且可能需要由调用者进行同步。

这告诉我们,当与ConcurrentDictionary. for each应该没问题,因为这将调用GetEnumerator具有本文开头提到的保证。我的预感是那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);
}
Run Code Online (Sandbox Code Playgroud)

可能,但不能保证,无论如何,这不会给您带来任何好处ToArray(),因此我会将此处的所有内容替换为:

KeyValuePair<int, Dto>[] dtoArray = concurrentDictionary.ToArray();
Run Code Online (Sandbox Code Playgroud)

其次:

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

这是否安全ConcurrentDictionary与您NameDto. 是的,您可以Dto安全地访问,但是您访问的任何属性或方法都需要是线程安全的。如果你ToArray()按照上面的建议使用,那么

Dto dto = dtoArray[0].Value
Run Code Online (Sandbox Code Playgroud)

是线程安全的,但是

string name = dtoArray[0].Value.Name
Run Code Online (Sandbox Code Playgroud)

取决于 的实现Name