Як видалити один конкретний об'єкт із ConcurrentBag <>?


109

З новим ConcurrentBag<T>в .NET 4, як видалити певний, конкретний об'єкт з нього , коли тільки TryTake()й TryPeek()доступні?

Я думаю про використання, TryTake()а потім просто додавання отриманого об'єкта до списку, якщо я не хочу його видаляти, але я відчуваю, що я можу щось пропустити. Це правильний шлях?

Відповіді:


89

Коротка відповідь: ви не можете зробити це просто.

ConcurrentBag зберігає локальну чергу для кожного потоку, і він переглядає черги інших потоків лише після того, як його власна черга стане порожньою. Якщо ви виймете предмет і покладете його назад, наступний вилучений елемент може бути знову тим самим. Немає гарантії, що багаторазове видалення та повернення їх дозволить повторити всі предмети.

Дві альтернативи для вас:

  • Видаліть усі предмети і запам’ятайте їх, поки не знайдете той, який хочете вийняти, а потім покладіть інші потім. Зауважте, що якщо дві нитки намагаються зробити це одночасно, у вас виникнуть проблеми.
  • Використовуйте більш підходящу структуру даних, таку як ConcurrentDictionary .

9
SynchronizedCollection також може бути підходящим замінником.
ІЛІЯ БРУДНО

2
@ILIABROUDNO - ви повинні ставити це як відповідь! Це НАБАГО краще, ніж незграбний паралельний словник, коли вам не потрібен словник
Денис

2
FYI, SynchronizedCollection недоступний у .NET Core. На дату цього коментаря типи System.Collections.Concurrent - це поточний шлях для реалізації .NET Core-реалізацій.
Меттью Снайдер

2
Я не впевнений, яку версію .NET Core використовували, але я працюю над проектом, заснованим на SDK .NET Core 2.1 і SynchronizedCollection, який зараз доступний у просторі імен Collections.Generic.
Лукас Леблан

15

Ви не можете. Це мішок, він не замовлений. Поклавши його назад, ви просто застрягнете в нескінченному циклі.

Ви хочете набір. Ви можете імітувати його за допомогою ConcurrentDictionary. Або HashSet, який захищаєш себе замком.


8
Будь ласка, розгорніть. Що б ви використовували як ключ у базовому ConcurrentDictionary?
Деніз Скідмор

2
Ну, я припускаю, що ключовим буде тип об'єкта, який ви намагаєтесь зберегти, і тоді значення буде колекцією якихось видів. Це б "наслідувало" а, HashSetяк він описує.
Mathias Lykkegaard Lorenzen

5

ConcurrentBag чудово обробляє список, куди ви можете додавати елементи та перераховувати з багатьох потоків, а потім, зрештою, викинути його, як підказує його назва :)

Як розповів Марк Байєрс , ви можете заново створити новий ConcurrentBag, який не містить елемент, який ви хочете видалити, але вам потрібно захистити це від декількох звернень у потоках за допомогою блокування. Це однолінійний:

myBag = new ConcurrentBag<Entry>(myBag.Except(new[] { removedEntry }));

Це працює і відповідає духу, для якого ConcurrentBag був розроблений.


9
Я вважаю, що ця відповідь вводить в оману. Щоб було зрозуміло, це НЕ забезпечує жодної безпеки потоку в потрібній операції видалення. А надягаючи замок навколо нього, начебто перемагає мета використання паралельної колекції.
ILIA BROUDNO

1
Я згоден. Ну а для уточнення, ConcurrentBag призначений для заповнення, перерахування та викидання всього вмісту, коли це зроблено. Будь-які спроби - включаючи міну - вилучити предмет, призведуть до забруднення зламу. Принаймні, я намагався надати відповідь, хоча найкраще використовувати кращий одночасний клас колекції, як ConcurrentDictionary.
Ларрі

4

Позначити правильно, що параметр ConcurrentDictionary буде працювати так, як ви хочете. Якщо ви все ще бажаєте використовувати ConcurrentBag , ви потрапите до вас наступним, неефективним розумом.

var stringToMatch = "test";
var temp = new List<string>();
var x = new ConcurrentBag<string>();
for (int i = 0; i < 10; i++)
{
    x.Add(string.Format("adding{0}", i));
}
string y;
while (!x.IsEmpty)
{
    x.TryTake(out y);
    if(string.Equals(y, stringToMatch, StringComparison.CurrentCultureIgnoreCase))
    {
         break;
    }
    temp.Add(y);
}
foreach (var item in temp)
{
     x.Add(item);
}

3

Як ви згадуєте, TryTake()це єдиний варіант. Це також приклад на MSDN . Рефлектор також не показує інших прихованих внутрішніх методів, що цікавлять.


1
public static void Remove<T>(this ConcurrentBag<T> bag, T item)
{
    while (bag.Count > 0)
    {
        T result;
        bag.TryTake(out result);

        if (result.Equals(item))
        {
            break; 
        }

        bag.Add(result);
    }

}

ConcurrentBagце невпорядкована колекція, але ваш код очікує , що bag.TryTakeі bag.Addробота в порядку FIFO. Ваш код передбачає, що bagвключає item, він циклічне, поки не знайде itemв bag. Відповіді лише на коди відмовляють, ви повинні пояснити своє рішення.
GDavid

-1

Це мій розширений клас, який я використовую у своїх проектах. Він може видалити один предмет з ConcurrentBag, а також може видалити список предметів з мішка

public static class ConcurrentBag
{
    static Object locker = new object();

    public static void Clear<T>(this ConcurrentBag<T> bag)
    {
        bag = new ConcurrentBag<T>();
    }


    public static void Remove<T>(this ConcurrentBag<T> bag, List<T> itemlist)
    {
        try
        {
            lock (locker)
            {
                List<T> removelist = bag.ToList();

                Parallel.ForEach(itemlist, currentitem => {
                    removelist.Remove(currentitem);
                });

                bag = new ConcurrentBag<T>();


                Parallel.ForEach(removelist, currentitem =>
                {
                    bag.Add(currentitem);
                });
            }

        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }

    public static void Remove<T>(this ConcurrentBag<T> bag, T removeitem)
    {
        try
        {
            lock (locker)
            {
                List<T> removelist = bag.ToList();
                removelist.Remove(removeitem);                

                bag = new ConcurrentBag<T>();

                Parallel.ForEach(removelist, currentitem =>
                {
                    bag.Add(currentitem);
                });
            }

        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
}

Важко повірити, що це може працювати, тому що ви створюєте новий ConcurrentBag в локальній змінній, але я не впевнений. будь-який тест?
шце8

3
Ні, це не працює. Як це має працювати, ви створюєте лише нову довідку. Стара все ще вказує на старий об’єкт .... Це спрацювало б, якщо ви працюєте з "ref"
Томас Крістоф

-5
public static ConcurrentBag<String> RemoveItemFromConcurrentBag(ConcurrentBag<String> Array, String Item)
{
    var Temp=new ConcurrentBag<String>();
    Parallel.ForEach(Array, Line => 
    {
       if (Line != Item) Temp.Add(Line);
    });
    return Temp;
}

-13

як щодо:

bag.Where(x => x == item).Take(1);

Це працює, я не впевнений, наскільки ефективно ...


Це нічого не виймає з сумки. Предмет, який ви отримуєте, залишається всередині сумки.
Кіт

3
повинен бути "bag = new ConcurrentBag (bag.Where (x => x! = item))"
atikot

4
@atikot, цей рядок змусив мене сміятися
parek
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.