Wie entferne ich ein einzelnes bestimmtes Objekt aus einem ConcurrentBag <>?


109

Mit dem neuen ConcurrentBag<T>in .NET 4, wie entfernen Sie ein bestimmtes, spezifisches Objekt aus ihm , wenn nur TryTake()und TryPeek()verfügbar sind?

Ich denke darüber nach TryTake(), das resultierende Objekt zu verwenden und es dann wieder in die Liste aufzunehmen, wenn ich es nicht entfernen möchte, aber ich habe das Gefühl, dass mir etwas fehlt. Ist das der richtige Weg?

Antworten:


89

Die kurze Antwort: Sie können es nicht einfach machen.

Das ConcurrentBag führt für jeden Thread eine lokale Thread-Warteschlange und überprüft die Warteschlangen anderer Threads erst, wenn die eigene Warteschlange leer ist. Wenn Sie ein Element entfernen und zurücklegen, ist das nächste Element, das Sie entfernen, möglicherweise wieder dasselbe Element. Es gibt keine Garantie dafür, dass Sie durch wiederholtes Entfernen und Zurücksetzen von Elementen alle Elemente durchlaufen können.

Zwei Alternativen für Sie:

  • Entfernen Sie alle Elemente und merken Sie sich diese, bis Sie das Element gefunden haben, das Sie entfernen möchten, und setzen Sie die anderen anschließend wieder ein. Beachten Sie, dass Probleme auftreten, wenn zwei Threads dies gleichzeitig versuchen.
  • Verwenden Sie eine geeignetere Datenstruktur wie ConcurrentDictionary .

9
SynchronizedCollection kann auch ein geeigneter Ersatz sein.
ILIA BROUDNO

2
@ILIABROUDNO - das solltest du als Antwort setzen! Das ist so viel besser als ein kludgey ConcurrentDictionary, wenn Sie kein Wörterbuch benötigen
Denis

2
Zu Ihrer Information, SynchronizedCollection ist in .NET Core nicht verfügbar. Ab dem Datum dieses Kommentars sind die System.Collections.Concurrent-Typen der derzeitige Weg für .NET Core-basierte Implementierungen.
Matthew Snyder

2
Ich bin nicht sicher, welche Version von .NET Core verwendet wurde, arbeite jedoch an einem Projekt, das auf dem .NET Core 2.1 SDK basiert, und SynchronizedCollection ist jetzt im Collections.Generic-Namespace verfügbar.
Lucas Leblanc

15

Das kannst du nicht. Es ist eine Tasche, es wird nicht bestellt. Wenn Sie es zurücklegen, bleiben Sie einfach in einer Endlosschleife stecken.

Du willst ein Set. Sie können eine mit ConcurrentDictionary emulieren. Oder ein HashSet, das Sie mit einem Schloss schützen.


8
Bitte erweitern. Was würden Sie als Schlüssel im zugrunde liegenden ConcurrentDictionary verwenden?
Denise Skidmore

2
Nun, ich nehme an, der Schlüssel wäre der Typ des Objekts, das Sie speichern möchten, und dann wäre der Wert eine Art Sammlung. Das würde a "emulieren", HashSetwie er es beschreibt.
Mathias Lykkegaard Lorenzen

5

Die ConcurrentBag eignet sich hervorragend für die Verarbeitung einer Liste, in der Sie Elemente hinzufügen und aus vielen Threads aufzählen und sie schließlich wegwerfen können, wie der Name schon sagt :)

Wie Mark Byers sagte , können Sie eine neue ConcurrentBag neu erstellen, die nicht das Element enthält, das Sie entfernen möchten. Sie müssen dies jedoch mit einer Sperre vor mehreren Thread-Treffern schützen. Dies ist ein Einzeiler:

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

Dies funktioniert und entspricht dem Geist, für den die ConcurrentBag entwickelt wurde.


9
Ich halte diese Antwort für irreführend. Um klar zu sein, bietet dies KEINE Gewindesicherheit im gewünschten Entfernungsvorgang. Und das Setzen eines Schlosses macht den Zweck der gleichzeitigen Verwendung einer Sammlung zunichte.
ILIA BROUDNO

1
Genau. Um ein wenig zu verdeutlichen, ist die ConcurrentBag so konzipiert, dass sie nach Abschluss des Vorgangs mit dem gesamten Inhalt gefüllt, aufgezählt und weggeworfen wird. Alle Versuche - einschließlich meiner -, einen Gegenstand zu entfernen, führen zu einem schmutzigen Hack. Zumindest habe ich versucht, eine Antwort zu geben, obwohl das Beste darin besteht, eine bessere gleichzeitige Sammlungsklasse wie das ConcurrentDictionary zu verwenden.
Larry

4

Mark ist insofern richtig, als das ConcurrentDictionary so funktioniert, wie Sie es möchten. Wenn Sie weiterhin eine ConcurrentBag verwenden möchten , werden Sie mit den folgenden, nicht effizienten, wohlgemerkt dorthin gebracht.

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

Wie Sie bereits erwähnt haben, TryTake()ist dies die einzige Option. Dies ist auch das Beispiel für MSDN . Der Reflektor zeigt auch keine anderen versteckten internen Methoden von Interesse.


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

}

ConcurrentBagist eine ungeordnete Sammlung, aber Ihr Code erwartet dies bag.TryTakeund bag.Addarbeitet auf FIFO-Weise. Der Code geht davon aus, dass bagbeinhaltet item, sie durchlaufen , bis es findet itemin bag. Von nur Code-Antworten wird abgeraten. Sie sollten Ihre Lösung erläutern.
GDavid

-1

Dies ist meine Erweiterungsklasse, die ich in meinen Projekten verwende. Es kann einen einzelnen Artikel aus ConcurrentBag entfernen und auch eine Liste von Artikeln aus der Tasche entfernen

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

Es ist schwer zu glauben, dass es funktionieren kann, weil Sie eine neue ConcurrentBag in einer lokalen Variablen erstellen, aber ich bin mir nicht sicher. irgendein Test?
Shtse8

3
Nein, es funktioniert nicht. Wie soll es funktionieren, Sie erstellen nur eine neue Referenz. Der alte zeigt immer noch auf das alte Objekt ... Es würde funktionieren, wenn Sie mit "ref" arbeiten
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

wie wäre es mit:

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

Es funktioniert, ich bin mir nicht sicher, wie effizient ...


Dadurch wird nichts aus der Tasche entfernt. Der Gegenstand, den Sie abrufen, verbleibt in der Tasche.
Keith

3
sollte "bag = new ConcurrentBag (bag.Where (x => x! = item))" sein
atikot

4
@atikot, diese Zeile brachte mich zum Lachen
parek
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.