Как удалить отдельный конкретный объект из ConcurrentBag <>?


109

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

Я подумываю использовать, TryTake()а затем просто добавить полученный объект обратно в список, если я не хочу его удалять, но мне кажется, что я что-то упускаю. Это правильный способ?

Ответы:


89

Короткий ответ: вы не можете сделать это простым способом.

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

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

  • Удалите все предметы и запомните их, пока не найдете тот, который хотите удалить, а затем верните остальные. Обратите внимание: если два потока попытаются сделать это одновременно, у вас возникнут проблемы.
  • Используйте более подходящую структуру данных, например ConcurrentDictionary .

9
SynchronizedCollection также может быть подходящей заменой.
— ИЛИЯ БРОУДНО,

2
@ILIABROUDNO - вы должны указать это как ответ! Это НАМНОГО лучше, чем беспорядочный ConcurrentDictionary, когда вам не нужен словарь,
— Денис

2
К вашему сведению, SynchronizedCollection недоступен в .NET Core. На момент написания этого комментария типы System.Collections.Concurrent являются текущим способом реализации реализаций на основе .NET Core.
— Мэтью Снайдер,

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

15

Вы не можете. Это сумка, это не заказано. Когда вы положите его обратно, вы просто застрянете в бесконечной петле.

Вам нужен набор. Вы можете эмулировать его с помощью ConcurrentDictionary. Или HashSet, который вы защищаете блокировкой.


8
Пожалуйста, расширьте. Что бы вы использовали в качестве ключа в базовом ConcurrentDictionary?
— Дениз Скидмор

2
Что ж, я предполагаю, что ключом будет тип объекта, который вы пытаетесь сохранить, а затем значение будет некоторой коллекцией. Это будет "подражать", HashSetкак он описывает.
— Матиас Ликкегаард Лоренцен

5

ConcurrentBag отлично подходит для обработки списка, в котором вы можете добавлять элементы и перечислять из многих потоков, а затем в конечном итоге выбросить его, как предполагает его название :)

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

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

Это работает и соответствует духу, для которого был разработан ConcurrentBag.


9
Я считаю, что этот ответ вводит в заблуждение. Чтобы быть ясным, это НЕ обеспечивает никакой потокобезопасности в желаемой операции удаления. И установка блокировки вокруг этого как бы побеждает цель использования параллельной коллекции.
— ИЛИЯ БРОУДНО,

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 . Reflector также не показывает никаких других скрытых внутренних методов, представляющих интерес.


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 в локальной переменной, но я не уверен. любой тест?
— shtse8 06

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))»
— атикот

4
@atikot, эта фраза меня рассмешила
— Parek
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.