¿Cómo eliminar un único objeto específico de un ConcurrentBag <>?


109

Con lo nuevo ConcurrentBag<T>en .NET 4, ¿cómo se elimina un determinado objeto específico cuando solo TryTake()y TryPeek()están disponibles?

Estoy pensando en usar TryTake()y luego agregar el objeto resultante nuevamente a la lista si no quiero eliminarlo, pero siento que me falta algo. ¿Es esta la forma correcta?

Respuestas:


89

La respuesta corta: no puedes hacerlo de una manera fácil.

ConcurrentBag mantiene una cola local de subprocesos para cada subproceso y solo mira las colas de otros subprocesos una vez que su propia cola se vacía. Si quita un elemento y lo vuelve a colocar, el siguiente elemento que elimine puede ser el mismo elemento nuevamente. No hay garantía de que quitar elementos repetidamente y volver a colocarlos le permitirá iterar sobre todos los elementos.

Dos alternativas para ti:

  • Elimine todos los elementos y recuérdelos hasta que encuentre el que desea eliminar, luego vuelva a colocar los demás. Tenga en cuenta que si dos subprocesos intentan hacer esto simultáneamente, tendrá problemas.
  • Utilice una estructura de datos más adecuada, como ConcurrentDictionary .

9
SynchronizedCollection también podría ser un sustituto adecuado.
ILIA BROUDNO

2
@ILIABROUDNO - ¡Deberías poner eso como respuesta! Eso es MUCHO mejor que un kludgey ConcurrentDictionary cuando no necesitas un diccionario
Denis

2
Para su información, SynchronizedCollection no está disponible en .NET Core. A la fecha de este comentario, los tipos System.Collections.Concurrent son el camino actual a seguir para las implementaciones basadas en .NET Core.
Matthew Snyder

2
No estoy seguro de qué versión de .NET Core se estaba usando, pero estoy trabajando en un proyecto basado en .NET Core 2.1 SDK y SynchronizedCollection está disponible en el espacio de nombres Collections.Generic ahora.
Lucas Leblanc

15

No puedes. Es una bolsa, no está ordenada. Cuando lo vuelvas a poner, te quedarás atrapado en un bucle sin fin.

Quieres un set. Puede emular uno con ConcurrentDictionary. O un HashSet que te proteges con un candado.


8
Por favor expanda ¿Qué usaría como clave en el ConcurrentDictionary subyacente?
Denise Skidmore

2
Bueno, supongo que la clave sería el tipo de objeto que estás intentando almacenar, y luego el valor sería una colección de algún tipo. Eso "emularía" a HashSetcomo él describe.
Mathias Lykkegaard Lorenzen

5

El ConcurrentBag es excelente para manejar una lista donde puede agregar elementos y enumerar de muchos subprocesos, y luego desecharlo como sugiere su nombre :)

Como dijo Mark Byers , puede reconstruir un nuevo ConcurrentBag que no contenga el elemento que desea eliminar, pero debe protegerlo contra múltiples subprocesos usando un candado. Este es un resumen:

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

Esto funciona y coincide con el espíritu para el que se ha diseñado ConcurrentBag.


9
Siento que esta respuesta es engañosa. Para ser claros, esto NO proporciona ninguna seguridad de rosca en la operación de extracción deseada. Y ponerle un candado frustra el propósito de usar una colección concurrente.
ILIA BROUDNO

1
Estoy de acuerdo. Bueno, para aclarar un poco, el ConcurrentBag está diseñado para ser llenado, enumerado y desechado con todo su contenido cuando esté listo. Cualquier intento, incluido el mío, de eliminar un elemento resultará en un truco sucio. Al menos traté de dar una respuesta, aunque lo mejor es usar una mejor clase de colección concurrente, como ConcurrentDictionary.
Larry

4

Mark tiene razón en que el ConcurrentDictionary funcionará de la manera que usted desea. Si aún desea usar un ConcurrentBag, lo siguiente, no eficiente, lo llevará allí.

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

Como mencionas, TryTake()es la única opción. Este es también el ejemplo en MSDN . Reflector tampoco muestra otros métodos internos ocultos de interés.


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

}

ConcurrentBages una colección desordenada, pero su código espera eso bag.TryTakey bag.Addfunciona de manera FIFO. Su código asume que bagincluye item, se repite hasta que encuentra itemen bag. No se recomiendan las respuestas de solo código, debe explicar su solución.
GDavid

-1

Esta es mi clase de extensión que estoy usando en mis proyectos. Puede eliminar un solo artículo de ConcurrentBag y también puede eliminar la lista de artículos de la bolsa

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 difícil creer que pueda funcionar porque está creando un nuevo ConcurrentBag en una variable local, pero no estoy seguro. alguna prueba?
shtse8

3
No, no funciona. ¿Cómo debería funcionar? Está creando una referencia nueva. El antiguo sigue apuntando al objeto antiguo ... Funcionaría si trabajas 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

qué tal si:

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

Funciona, no estoy seguro de cuán eficientemente ...


Esto no quita nada de la bolsa. El artículo que está recuperando permanece dentro de la bolsa.
Keith

3
debe ser "bag = new ConcurrentBag (bag.Where (x => x! = item))"
atikot

4
@atikot, esa línea me hizo reír
parek
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.