2016-12-08 22 views
6

У меня возникла интересная проблема. Зная, что ConcurrentDictionary<TKey, TValue> безопасно перечислим при изменении, с (в моем случае) нежелательным побочным эффектом повторения элементов, которые могут исчезать или появляться несколько раз, я решил создать снимок самостоятельно, используя ToList(). Так как ConcurrentDictionary<TKey, TValue> также реализует ICollection<KeyValuePair<TKey, TValue>>, это приводит к использованию List(IEnumerable<T> collection), который, в свою очередь, создает массив в текущем размере словаря с использованием текущего элемента Count, затем пытается скопировать элементы using ICollection<T>.CopyTo(T[] array, int arrayIndex), вызвав его реализацию ConcurrentDictionary<TKey, TValue> и, наконец, бросая ArgumentException, если элементы добавляются в словарь тем временем.Вызов ToList() на ConcurrentDictionary <TKey, TValue> при добавлении элементов

Блокировка на всем уровне будет убивать точку использования коллекции, так как мои параметры, похоже, либо должны улавливать исключение и повторять попытку (что определенно не является правильным ответом на проблему), либо реализовать мои собственная версия ToList(), специализирующаяся на этой проблеме (но опять же, просто увеличивая список, а затем, возможно, подрезая его до нужного размера, для нескольких элементов кажется излишним, а использование LinkedList уменьшит производительность индексации).

Кроме того, похоже, добавив некоторые методы LINQ, которые создают своего рода буфер в фоновом режиме (например, как OrderBy), кажется, исправить эту проблему за счет производительности, но голый ToList() явно не делает, и это не стоит «дополнять» его другим методом, если не требуется никаких дополнительных функций.

Это может быть проблема с любой параллельной коллекцией?

Каково было бы разумное обходное решение, позволяющее свести производительность до минимума при создании такого моментального снимка? (Предпочтительно в конце некоторого LINQ магии.)

Edit:

Посмотрев на него, я могу подтвердить, ToArray() (думать, что я просто проходил мимо него вчера) действительно решить проблему моментальных снимков, как поскольку это просто, простой снимок, это не помогает, когда требуется дополнительная функциональность, прежде чем принимать указанный снимок (например, фильтрацию, сортировку), а список/массив по-прежнему необходим в конце. (В этом случае требуется дополнительный вызов, снова создавая новую коллекцию.)

Я не указал, что для моментального снимка может потребоваться или не понадобиться выполнить эти изменения, поэтому его следует принять на конечно, поэтому я бы добавил это к вопросам.

(Кроме того, если кто-нибудь имеют лучшую идею для названия, не говорят.)

+2

Используйте '.ToArray()' вместо этого, который реализуется специально по типу. –

ответ

6

Давайте ответим на широком чрезмерно затенение вопроса здесь для всех параллельных типов:

Если вы разбили операция, которая имеет дело с внутренностями в несколько этапов, где все шаги должны быть «синхронизированы», затем да, окончательно вы получите сбои и нечетные результаты из-за синхронизации потоков.

Так что, если с помощью .ToList() сначала попросить .Count, то размер массива, а затем использовать foreach, чтобы захватить значения и место в списке, то да, окончательно вы будете иметь шанс из двух частей получая различное количество элементов.

Чтобы быть честным, я хотел бы, чтобы некоторые из этих параллельных типов не пытались притвориться, что они являются обычными коллекциями, реализуя множество этих интерфейсов, но, увы, так оно и есть.

Можете ли вы исправить свой код, теперь, когда вы знаете о проблеме?

Да, вы можете взглянуть на документацию по типу и посмотреть, не предоставляет ли он какой-либо формы механизма снимка, который не подвержен вышеупомянутым проблемам.

Оказывается ConcurrentDictionary<TKey, TValue> орудия .ToArray(), который является documented с:

новый массив, содержащий снимок ключевых и пар значений, скопированных из System.Collections.Concurrent.ConcurrentDictionary.

(курсив мой)

Как .ToArray() в настоящее время реализуется?

Using locks см линию 697.

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

Кроме того, метод .GetEnumerator() следует некоторые из тех же правил, от documentation:

Перечислитель вернулся из словаря безопасно использовать одновременно с чтением и записью в словарь, однако это делает не представляют моментальный снимок словаря. Содержимое , отображаемое через счетчик, может содержать изменения, внесенные в словарь после того, как GetEnumerator был вызван.

(опять же, мой emhpasis)

Так что пока .GetEnumerator() не аварии, он может не давать желаемых результатов.

В зависимости от сроков, ни от .ToArray(), так что все зависит.