Потокобезопасный foreach перечисление списков - PullRequest
10 голосов
/ 16 сентября 2008

Мне нужно перечислить хотя бы общий IList <> объектов. Содержимое списка может измениться, как при добавлении или удалении другими потоками, и это приведет к уничтожению моего перечисления: «Коллекция изменена; операция перечисления может не выполняться».

Что такое хороший способ сделать потокобезопасный foreach в IList <>? желательно без клонирования всего списка. Невозможно клонировать реальные объекты, на которые ссылается список.

Ответы [ 10 ]

12 голосов
/ 16 сентября 2008

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

3 голосов
/ 16 сентября 2008

Вы найдете, что это очень интересная тема.

Наилучший подход основан на ReadWriteResourceLock, который используется, чтобы иметь большие проблемы с производительностью из-за так называемой проблемы Convoy.

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

3 голосов
/ 16 сентября 2008

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

На ум приходит несколько вариантов:

  • Клонировать список. Теперь у каждого перечислителя есть своя собственная копия для работы.
  • Сериализация доступа к списку. Используйте блокировку, чтобы убедиться, что никакой другой поток не может изменить ее во время перечисления.

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

2 голосов
/ 16 сентября 2008

Нет такой операции. Лучшее, что вы можете сделать, это


lock(collection){
    foreach (object o in collection){
       ...
    }
}
1 голос
/ 07 июля 2009

Поведение по умолчанию для простой индексированной структуры данных, такой как связанный список, b-дерево или хеш-таблица, состоит в перечислении в порядке от первого до последнего. Это не вызовет проблемы при вставке элемента в структуру данных после того, как итератор уже достигнет этой точки, или при вставке элемента, который итератор будет перечислять после его прибытия, и такое событие может быть обнаружено приложением и обработано, если приложение требовало этого. Чтобы обнаружить изменение в коллекции и выдать ошибку во время перечисления, я мог только представить, что это была чья-то (плохая) идея сделать то, что, как они думали, захочет программист. Действительно, Microsoft исправила их коллекции для правильной работы. Они назвали свои блестящие новые непрерывные коллекции ConcurrentCollections (System.Collections.Concurrent) в .NET 4.0.

1 голос
/ 16 сентября 2008

Итак, требования таковы: вам нужно перечислять через IList <>, не создавая копию, одновременно добавляя и удаляя элементы.

Не могли бы вы уточнить несколько вещей? Вставки и удаления происходят только в начале или конце списка? Если изменения могут произойти в любой точке списка, как должно вести себя перечисление, когда элементы удаляются или добавляются рядом или в месте нахождения текущего элемента перечисления?

Это, безусловно, выполнимо путем создания пользовательского объекта IEnumerable с, возможно, целочисленным индексом, но только если вы можете контролировать весь доступ к вашему объекту IList <> (для блокировки и поддержания состояния вашего перечисления). Но многопоточное программирование - сложная задача в лучших обстоятельствах, и это сложная вероятность.

1 голос
/ 16 сентября 2008
ICollection MyCollection;
// Instantiate and populate the collection
lock(MyCollection.SyncRoot) {
  // Some operation on the collection, which is now thread safe.
}

С MSDN

1 голос
/ 16 сентября 2008

Forech зависит от того, что коллекция не изменится. Если вы хотите перебрать коллекцию, которая может измениться, используйте нормальную конструкцию for и будьте готовы к недетерминированному поведению. Блокировка может быть лучшей идеей, в зависимости от того, что вы делаете.

0 голосов
/ 22 февраля 2019

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

Во многих случаях вы можете использовать старый добрый цикл for и немедленно назначить объект копии для использования внутри цикла. Просто имейте в виду, что все потоки, записывающие объекты вашего списка, должны записывать разные данные объектов. В противном случае используйте блокировку или копию, как предлагают другие участники.

Пример:

foreach(var p in Points)
{
    // work with p...
}

Может быть заменено на:

for(int i = 0; i < Points.Count; i ++)
{
   Point p = Points[i];
   // work with p...
}
0 голосов
/ 16 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...