修改ConcurrentBag中项目的可接受方法



考虑以下函数,该函数在通用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的线程,一个使用idToFind_1,另一个使用在同一ConcurrentBag<T> Items上工作的idToFind_2。然后,第一个线程可能会取出第二个线程需要更新的项目。因此,具有idToFind_2的项目很有可能会错过更新,反之亦然。这里我们有竞赛条件:如果thread1及时放回项目,它将得到更新,否则不会。

此外,您仍然需要处理这样一个事实,即您正在更改从多个线程访问的项目,这是不安全的(Servy的评论)。

由于实施在某种程度上是低效的。您是否考虑过使用另一种更合适的数据结构,并可能通过使用任何其他代码块使用的锁来提供对数据结构的同一实例的独占访问,从而实现同步。

此外,由于tempItemsUpdateList的本地集合,因此不需要线程安全集合,因为同步中没有任何点。因此,一个简单的List<T>也就足够了。

参数不需要ref关键字,请参阅C#中何时使用ref以及何时不需要ref。

最新更新