Come rimuovere un singolo oggetto specifico da un ConcurrentBag <>?


109

Con il nuovo ConcurrentBag<T>in .NET 4, come si rimuove un determinato oggetto specifico da esso quando solo TryTake()e TryPeek()sono disponibili?

Sto pensando di utilizzare TryTake()e quindi aggiungere nuovamente l'oggetto risultante all'elenco se non voglio rimuoverlo, ma mi sento come se mi mancasse qualcosa. È questo il modo corretto?

Risposte:


89

La risposta breve: non puoi farlo in modo semplice.

Il ConcurrentBag mantiene una coda locale di thread per ogni thread e guarda solo le code degli altri thread quando la sua coda diventa vuota. Se rimuovi un elemento e lo rimetti a posto, l'elemento successivo che rimuovi potrebbe essere di nuovo lo stesso elemento. Non vi è alcuna garanzia che la rimozione ripetuta di elementi e il loro reinserimento ti consentirà di scorrere tutti gli elementi.

Due alternative per te:

  • Rimuovi tutti gli elementi e ricordali, finché non trovi quello che desideri rimuovere, quindi rimetti gli altri in seguito. Nota che se due thread tentano di farlo contemporaneamente, avrai problemi.
  • Usa una struttura dati più adatta come ConcurrentDictionary .

9
SynchronizedCollection potrebbe anche essere un sostituto adatto.
ILIA BROUDNO

2
@ILIABROUDNO - dovresti metterlo come risposta! Questo è MOLTO meglio di un ConcurrentDictionary kludgey quando non hai bisogno di un dizionario
Denis

2
Cordiali saluti, SynchronizedCollection non è disponibile in .NET Core. Alla data di questo commento, i tipi System.Collections.Concurrent sono la strada da percorrere per le implementazioni basate su .NET Core.
Matthew Snyder

2
Non sono sicuro di quale versione di .NET Core fosse utilizzata, ma sto lavorando a un progetto basato su .NET Core 2.1 SDK e SynchronizedCollection è disponibile nello spazio dei nomi Collections.Generic ora.
Lucas Leblanc

15

Non puoi. È una borsa, non è ordinato. Quando lo rimetti a posto, rimarrai bloccato in un ciclo infinito.

Vuoi un set. Puoi emularne uno con ConcurrentDictionary. O un HashSet che ti proteggi con un lucchetto.


8
Si prega di espandere. Cosa useresti come chiave nel ConcurrentDictionary sottostante?
Denise Skidmore

2
Bene, presumo che la chiave sarebbe il tipo di oggetto che stai cercando di memorizzare, e quindi il valore sarebbe una raccolta di qualche tipo. Ciò "emulerebbe" un HashSetcome descrive.
Mathias Lykkegaard Lorenzen

5

Il ConcurrentBag è ottimo per gestire un elenco in cui è possibile aggiungere elementi ed enumerare da molti thread, quindi eventualmente buttarlo via come suggerisce il nome :)

Come ha detto Mark Byers , puoi ricostruire un nuovo ConcurrentBag che non contiene l'oggetto che desideri rimuovere, ma devi proteggerlo da più thread colpi usando un lucchetto. Questa è una battuta:

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

Funziona e corrisponde allo spirito per cui è stato progettato ConcurrentBag.


9
Penso che questa risposta sia fuorviante. Per essere chiari, questo NON fornisce alcuna sicurezza del filo nell'operazione di rimozione desiderata. E mettere un lucchetto attorno ad esso vanifica lo scopo di utilizzare una raccolta simultanea.
ILIA BROUDNO

1
Sono d'accordo. Bene, per chiarire un po ', il ConcurrentBag è progettato per essere riempito, enumerato e gettato via con tutto il suo contenuto quando è finito. Qualsiasi tentativo, incluso il mio, di rimuovere un oggetto risulterà in un hack sporco. Almeno ho provato a fornire una risposta, anche se la cosa migliore è usare una classe di raccolta simultanea migliore, come ConcurrentDictionary.
Larry

4

Mark ha ragione in quanto il ConcurrentDictionary funzionerà nel modo desiderato. Se desideri ancora utilizzare un ConcurrentBag, quanto segue, non è efficiente per te, ti porterà lì.

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

Come dici tu, TryTake()è l'unica opzione. Questo è anche l'esempio su MSDN . Reflector non mostra nemmeno altri metodi interni nascosti di 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);
    }

}

ConcurrentBagè una raccolta non ordinata, ma il tuo codice se lo aspetta bag.TryTakee bag.Addfunziona in modo FIFO. Il codice presume che bagincluda item, si ripete finché non trova itemin bag. Le risposte solo codice sono scoraggiate, dovresti spiegare la tua soluzione.
GDavid

-1

Questa è la mia classe di estensione che sto usando nei miei progetti. Può rimuovere un singolo articolo da ConcurrentBag e può anche rimuovere l'elenco di articoli dalla borsa

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

È difficile credere che possa funzionare perché stai creando un nuovo ConcurrentBag nella variabile locale, ma non ne sono sicuro. qualsiasi prova?
shtse8

3
No, non funziona. Come dovrebbe funzionare, stai creando solo un nuovo riferimento. Quello vecchio sta ancora indicando il vecchio oggetto .... Funzionerebbe se lavorassi con "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

che ne dite di:

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

Funziona, non sono sicuro di quanto sia efficiente ...


Questo non rimuove nulla dalla borsa. L'articolo che stai recuperando rimane all'interno della borsa.
Keith

3
dovrebbe essere "bag = new ConcurrentBag (bag.Where (x => x! = item))"
atikot

4
@atikot, quella frase mi ha fatto ridere
parek
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.