Многопоточный доступ к непотоковым значениям в System.Collection.Concurrent?

Если я использую коллекцию из пространства имен System.Collection.Concurrent, такую ​​как ConcurrentDictionary<K, V>, с типом ключа и/или значения, который не является потокобезопасным, например ConcurrentDictionary<int, Dictionary<int, int>>, Какие могут возникнуть проблемы?

Я предполагаю, что могу выполнять любые операции с самим ConcurrentDictionary, но что, если я создам System.Collections.Generic.List<T> из Dictionary<int, int> из этого ConcurrentDictionary и изменю его?

Здесь я создаю список с помощью LINQ в «одной строке», хотя я предполагаю, что после выполнения ToList я не в безопасности ConcurrentDictionary lock?

ConcurrentDictionary<int, Dictionary<int, int>> conDict = ...;
conDict.Select(x => x.Value).ToList().ForEach(x => x.Add(1, 2));

Если это безопасно или небезопасно, я предполагаю, что это слишком

ConcurrentDictionary<int, Dictionary<int, int>> conDict = ...;
var regDictList = conDict.Select(x => x.Value).ToList();
regDictList.ForEach(x => x.Add(1, 2));

person KDecker    schedule 26.08.2016    source источник


Ответы (1)


Что ж, ConcurrentDictionary сам по себе для доступа из нескольких потоков (TryAdd, TryGetValue и т. д.).

Важно понимать, что потокобезопасными являются не объекты, содержащиеся в ConcurrentDictionary, а сам ConcurrentDictionary.


Если вы получаете доступ к определенному значению, содержащемуся в словаре, из нескольких потоков, вам также необходимо убедиться, что оно является потокобезопасным.

Поскольку результат от:

conDict.Select(x => x.Value)

is:

IEnumerable<Dictionary<int, int>>

и это больше не имеет ничего общего с ConcurrentDictionary - List<Dictionary<int, int>>, полученным:

conDict.Select(x => x.Value).ToList()

конечно, НЕ потокобезопасен

person Tamir Vered    schedule 26.08.2016
comment
Итак, в моем примере выше, если я хочу сохранить поток conDict.Values безопасным, мне нужно будет изменить тип на ConcurrentDictionary<int, ConcurrentDictionary<int, int>>? - person KDecker; 26.08.2016
comment
@KDecker Да, но обратите внимание, что вы не должны изменять внешний ConcurrentDictionary из другого потока при переборе его значений (Как и любую коллекцию нельзя изменять во время итерации). - person Tamir Vered; 26.08.2016
comment
Каждый раз, когда вы изменяете конкретный объект из нескольких потоков, убедитесь, что этот конкретный объект является потокобезопасным. - person Tamir Vered; 26.08.2016
comment
@TamirVered ваше предупреждение об изменении коллекции не применяется к классам в пространстве имен System.Collections.Concurrent, все коллекции в этом пространстве имен делают снимок коллекции и повторяют этот снимок. По сути, каждый раз, когда вы выполняете foreach(var foo in myConcurrentDictionary) внутри себя, вы делаете что-то похожее на то, что вы делаете foreach(var foo in myConcurrentDictionary.ToList()). - person Scott Chamberlain; 26.08.2016
comment
@Scott Linq ленив, он перебирает исходную коллекцию до тех пор, пока не будет вызван .ToList или что-то в этом роде (которое повторяет IEnumerable) - person Tamir Vered; 26.08.2016
comment
Вот почему я сказал что-то похожее на то, что вы делаете, внутренне это намного сложнее, я просто пытался указать, что вы никогда не получите ошибку, на которую ссылаетесь в своем комментарии, из коллекции System.Collections.Concurrent - person Scott Chamberlain; 26.08.2016
comment
На самом деле, я был немного неправ, он не делает снимок, однако все еще безопасно изменять внешний словарь при перечислении, в MSDN указано, что это безопасно. - person Scott Chamberlain; 26.08.2016
comment
Вот веб-страница, которую я только что читал и которая, как мне кажется, может быть связана с этим поддискуссией Тамир и Скотт (arbel.net/2013/02/03/). В нем обсуждается механика моментальных снимков. - person KDecker; 26.08.2016