Приемлемый метод для изменения элемента в ConcurrentBag?

1

Рассмотрим следующую функцию, которая выполняет итерацию над общим List<T>: Items и изменяет соответствующий элемент, если он найден:

void UpdateList(ref List<clsMyClass> Items, int idToFind) {
    foreach(var Item in Items) {
        if (Item.ID == idToFind)
        {   
            // modify the item
            Item.SomeIntCounter++;
            return;
        }
    }
}

Теперь, если бы я хотел сделать то же самое, но на этот раз с помощью поточного ConcurrentBag<T>, это приемлемый метод?...

void UpdateList(ref ConcurrentBag<clsMyClass> Items, int idToFind) {
    clsMyClass Item;
    bool found = false;
    ConcurrentBag<clsMyClass> tempItems = new ConcurrentBag<clsMyClass>();
    while(Items.Count > 0) {
        if (Items.TryTake(out Item))
        {
            if (Item.ID == idToFind)
            {
                //modify the item
                Item.SomeIntCounter++;
                found = true;
            }
            tempItems.Add(Item);
            if (found) break;
        }
    }
    foreach(var tempItem in tempItems) Items.Add(tempItem);
}

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

Является ли это разумным способом изменения коллекции в потоковом безопасном режиме?

  • 1
    Если ваш ID уникален, я бы сказал, что ConcurrentDictionary более уместен. И нет, ваш второй метод не является поточно-ориентированным, так как вы не используете никаких блокировок и берете предметы, создаете временную сумку и т. Д., И все это может вызвать проблемы, если это происходит одновременно.
  • 0
    @Maarten Спасибо - идентификатор не всегда уникален, поскольку несохраненные записи имеют идентификатор 0 в соответствии с классами, используемыми в проекте. Кроме того, я думал, что пространство имен Concurrent использует свои собственные методы внутренней блокировки. Моя функция также использует то, что я думал, был потокобезопасный метод удаления элемента из ConcurrentBag: TryTake ()?
Показать ещё 2 комментария
Теги:
multithreading
collections

1 ответ

0

Ваша параллельная версия UpdateList не является потокобезопасной, поскольку она вводит условие гонки.

Ваша UpdateList версия UpdateList не эквивалентна вашей второй версии в случае многопоточности. Понимаете, почему? Если вы запускаете два потока, выполняющих UpdateList один с idToFind_1 а другой с idToFind_2 работающим с теми же idToFind_2 ConcurrentBag<T> Items. Тогда первый поток может вынуть элемент, который нужно обновить во втором. Поэтому есть вероятность, что элемент с idToFind_2 пропустит обновление и наоборот. И здесь у нас есть условие гонки: если thread1 вернет элемент обратно, он получит обновление, иначе оно не будет.

Кроме того, вам все равно придется иметь дело с тем фактом, что вы мутируете элементы, к которым обращаются из нескольких потоков, и что это не так безопасно (комментарий Servy).

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

Более того, поскольку tempItems является локальным для UpdateList вам не нужна потокобезопасная коллекция, поскольку синхронизация отсутствует. Поэтому достаточно простого List<T>.

Вам не нужно ключевое слово ref для параметров, см. Когда использовать ref и когда это не нужно в С#.

  • 1
    Код Ор не является безопасным. lock полезна только в том случае, если любой другой блок кода, обращающийся к этим данным, также блокируется в том же экземпляре. Объект не будет передан по ссылке без ключевого слова ref . Он будет передан по значению, но это значение будет ссылкой. Это очень важное различие.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню