Рассмотрим следующую функцию, которая выполняет итерацию над общим 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.
Является ли это разумным способом изменения коллекции в потоковом безопасном режиме?
Ваша параллельная версия UpdateList
не является потокобезопасной, поскольку она вводит условие гонки.
Ваша UpdateList
версия UpdateList
не эквивалентна вашей второй версии в случае многопоточности. Понимаете, почему? Если вы запускаете два потока, выполняющих UpdateList
один с idToFind_1
а другой с idToFind_2
работающим с теми же idToFind_2
ConcurrentBag<T> Items
. Тогда первый поток может вынуть элемент, который нужно обновить во втором. Поэтому есть вероятность, что элемент с idToFind_2
пропустит обновление и наоборот. И здесь у нас есть условие гонки: если thread1 вернет элемент обратно, он получит обновление, иначе оно не будет.
Кроме того, вам все равно придется иметь дело с тем фактом, что вы мутируете элементы, к которым обращаются из нескольких потоков, и что это не так безопасно (комментарий Servy).
Поскольку реализация в какой-то мере неэффективна. Вы думали об использовании другой более подходящей структуры данных и, возможно, позаботились о синхронизации, используя блокировку, которая используется любым другим блоком кода для обеспечения эксклюзивного доступа к тому же экземпляру структуры данных.
Более того, поскольку tempItems
является локальным для UpdateList
вам не нужна потокобезопасная коллекция, поскольку синхронизация отсутствует. Поэтому достаточно простого List<T>
.
Вам не нужно ключевое слово ref
для параметров, см. Когда использовать ref и когда это не нужно в С#.
lock
полезна только в том случае, если любой другой блок кода, обращающийся к этим данным, также блокируется в том же экземпляре. Объект не будет передан по ссылке без ключевого слова ref
. Он будет передан по значению, но это значение будет ссылкой. Это очень важное различие.
ID
уникален, я бы сказал, чтоConcurrentDictionary
более уместен. И нет, ваш второй метод не является поточно-ориентированным, так как вы не используете никаких блокировок и берете предметы, создаете временную сумку и т. Д., И все это может вызвать проблемы, если это происходит одновременно.