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

时间:2014-10-01 09:43:15

标签: c# multithreading collections

考虑以下函数,该函数迭代通用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 个答案:

答案 0 :(得分:-1)

UpdateList的并发版本不是线程安全的,因为它引入了race condition

在多线程的情况下,您的UpdateList的第一个版本与第二个版本不同。你明白为什么吗?如果您启动两个线程,UpdateList执行idToFind_1,另一个执行idToFind_2同一个ConcurrentBag<T> Items。然后第一个线程可能会取出第二个需要更新的项目。因此,idToFind_2的项目很可能会错过更新,反之亦然。在这里我们有竞争条件:如果thread1及时放回项目,它将获得更新否则它将不会。

  

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

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

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

参数不需要ref关键字,请参阅When to use ref and when it is not necessary in C#