Jak usunąć pojedynczy, określony obiekt z ConcurrentBag <>?


109

W nowej wersji ConcurrentBag<T>.NET 4, jak usunąć z niej określony, konkretny obiekt, gdy tylko TryTake()iTryPeek() jest on dostępny?

Myślę o użyciu, TryTake()a następnie po prostu dodaniu wynikowego obiektu z powrotem do listy, jeśli nie chcę go usuwać, ale wydaje mi się, że czegoś mi brakuje. Czy to właściwy sposób?

Odpowiedzi:


89

Krótka odpowiedź: nie da się tego zrobić w łatwy sposób.

ConcurrentBag przechowuje lokalną kolejkę wątku dla każdego wątku i przegląda kolejki innych wątków tylko wtedy, gdy jego własna kolejka stanie się pusta. Jeśli usuniesz element i odłożysz go z powrotem, następnym usuniętym elementem może być ponownie ten sam element. Nie ma gwarancji, że wielokrotne usuwanie elementów i umieszczanie ich z powrotem pozwoli ci na iterację po wszystkich elementach.

Dwie alternatywy dla Ciebie:

  • Usuń wszystkie elementy i zapamiętaj je, aż znajdziesz ten, który chcesz usunąć, a następnie odłóż pozostałe. Zauważ, że jeśli dwa wątki spróbują to zrobić jednocześnie, będziesz miał problemy.
  • Użyj bardziej odpowiedniej struktury danych, takiej jak ConcurrentDictionary .

9
SynchronizedCollection może być również odpowiednim zamiennikiem.
ILIA BROUDNO

2
@ILIABROUDNO - należy to odpowiedzieć! To DUŻO lepsze niż kludgey ConcurrentDictionary, kiedy nie potrzebujesz słownika
Denis

2
FYI, SynchronizedCollection nie jest dostępna w .NET Core. W dniu opublikowania tego komentarza typy System.Collections.Concurrent są obecnym sposobem implementacji opartych na platformie .NET Core.
Matthew Snyder

2
Nie mam pewności, która wersja programu .NET Core była używana, ale pracuję nad projektem opartym na zestawie SDK programu .NET Core 2.1 i SynchronizedCollection jest teraz dostępny w obszarze nazw Collections.Generic.
Lucas Leblanc

15

Nie możesz. To torba, nie jest zamówiona. Kiedy go odłożysz, po prostu utkniesz w niekończącej się pętli.

Chcesz zestaw. Możesz emulować jeden z ConcurrentDictionary. Lub zestaw HashSet, który chronisz za pomocą zamka.


8
Proszę rozwinąć. Czego użyłbyś jako klucza w bazowym ConcurrentDictionary?
Denise Skidmore,

2
Cóż, zakładam, że klucz byłby typem obiektu, który próbujesz przechowywać, a następnie wartością byłby pewnego rodzaju zbiór. To by „naśladowało” HashSetjak on opisuje.
Mathias Lykkegaard Lorenzen

5

ConcurrentBag świetnie radzi sobie z listą, w której możesz dodawać elementy i wyliczać z wielu wątków, a następnie ostatecznie wyrzucać, jak sugeruje nazwa :)

Jak powiedział Mark Byers , możesz ponownie zbudować nowy ConcurrentBag, który nie zawiera elementu, który chcesz usunąć, ale musisz go zabezpieczyć przed trafieniami wielu wątków za pomocą blokady. To jest jedna linijka:

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

To działa i pasuje do ducha, dla którego zaprojektowano ConcurrentBag.


9
Czuję, że ta odpowiedź jest myląca. Aby było jasne, NIE zapewnia to żadnego zabezpieczenia gwintów w żądanej operacji usuwania. A założenie wokół niego kłódki w pewnym sensie niweczy cel używania kolekcji równoległej.
ILIA BROUDNO

1
Zgadzam się. Cóż, żeby trochę wyjaśnić, ConcurrentBag jest zaprojektowany tak, aby po zakończeniu wypełniania, wyliczania i wyrzucania całej zawartości. Wszelkie próby - w tym moje - usunięcia przedmiotu spowodują nieczyste włamanie. Przynajmniej starałem się udzielić odpowiedzi, chociaż najlepiej jest użyć lepszej klasy kolekcji współbieżnej, takiej jak ConcurrentDictionary.
Larry,

4

Mark ma rację, ponieważ ConcurrentDictionary będzie działać tak, jak chcesz. Jeśli nadal chcesz korzystać z ConcurrentBag , zaprowadzą Cię tam poniższe (nie wydajne).

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

Jak wspomniałeś, TryTake()to jedyna opcja. To jest również przykład w MSDN . Reflector nie wykazuje również innych ukrytych, interesujących metod wewnętrznych.


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);
    }

}

ConcurrentBagjest nieuporządkowaną kolekcją, ale Twój kod tego oczekuje bag.TryTakei bag.Adddziała w sposób FIFO. Twój kod zakłada, że bagobejmuje itemon pętle, dopóki nie znajdzie się itemw bag. Tylko kod odpowiedzi są odradzane, powinieneś wyjaśnić swoje rozwiązanie.
GDavid

-1

To jest moja klasa rozszerzeń, której używam w moich projektach. Może usunąć pojedynczy przedmiot z ConcurrentBag, a także może usunąć listę przedmiotów z torby

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);
        }
    }
}

Trudno uwierzyć, że to zadziała, ponieważ tworzysz nowy ConcurrentBag w zmiennej lokalnej, ale nie jestem pewien. jakiś test?
shtse8

3
Nie, to nie działa. Jak to powinno działać, tworzysz tylko nowe odniesienie. Stary nadal wskazuje na stary obiekt .... To zadziała, jeśli będziesz pracować z „ref”
Thomas Christof

-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

Co powiesz na:

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

Działa, nie jestem pewien, jak skutecznie ...


To nie powoduje usunięcia niczego z torby. Przedmiot, który odzyskujesz, pozostaje w torbie.
Keith

3
powinno być "bag = new ConcurrentBag (bag.Where (x => x! = item))"
atikot

4
@atikot, ta linijka mnie rozśmieszyła
parek
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.