Como remover um único objeto específico de um ConcurrentBag <>?


109

Com o novo ConcurrentBag<T>no .NET 4, como você remove um determinado objeto específico dele quando apenas TryTake()e TryPeek()estão disponíveis?

Estou pensando em usar TryTake()e, em seguida, apenas adicionar o objeto resultante de volta à lista se não quiser removê-lo, mas sinto que pode estar faltando alguma coisa. Esta é a maneira correta?

Respostas:


89

A resposta curta: você não pode fazer isso de uma maneira fácil.

O ConcurrentBag mantém uma fila de thread local para cada thread e só olha as filas de outros threads quando sua própria fila fica vazia. Se você remover um item e colocá-lo de volta, o próximo item removido poderá ser o mesmo item novamente. Não há garantia de que remover itens repetidamente e colocá-los de volta permitirá a iteração de todos os itens.

Duas alternativas para você:

  • Remova todos os itens e lembre-se deles, até encontrar aquele que deseja remover e, em seguida, coloque os outros de volta. Observe que, se dois threads tentarem fazer isso simultaneamente, você terá problemas.
  • Use uma estrutura de dados mais adequada, como ConcurrentDictionary .

9
SynchronizedCollection também pode ser um substituto adequado.
ILIA BROUDNO

2
@ILIABROUDNO - você deveria colocar isso como resposta! Isso é MUITO melhor do que um ConcurrentDictionary kludgey quando você não precisa de um Dicionário
Denis

2
Para sua informação, SynchronizedCollection não está disponível no .NET Core. Na data deste comentário, os tipos System.Collections.Concurrent são o caminho atual para as implementações baseadas no .NET Core.
Matthew Snyder

2
Não tenho certeza de qual versão do .NET Core estava sendo usada, mas estou trabalhando em um projeto baseado no SDK do .NET Core 2.1 e SynchronizedCollection está disponível no namespace Collections.Generic agora.
Lucas Leblanc

15

Você não pode. É um saco, não foi encomendado. Ao colocá-lo de volta, você ficará preso em um loop infinito.

Você quer um Conjunto. Você pode emular um com ConcurrentDictionary. Ou um HashSet que você se protege com um cadeado.


8
Por favor, expanda. O que você usaria como chave no ConcurrentDictionary subjacente?
Denise Skidmore

2
Bem, suponho que a chave seria o tipo de objeto que você está tentando armazenar e, em seguida, o valor seria uma coleção de alguns tipos. Isso iria "emular" a HashSetcomo ele descreve.
Mathias Lykkegaard Lorenzen

5

O ConcurrentBag é ótimo para lidar com uma lista onde você pode adicionar itens e enumerar de muitos encadeamentos e, em seguida, jogá-lo fora, conforme seu nome sugere :)

Como Mark Byers disse , você pode reconstruir um novo ConcurrentBag que não contenha o item que deseja remover, mas você deve protegê-lo contra múltiplos acessos de threads usando um bloqueio. Este é um one-liner:

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

Isso funciona e corresponde ao espírito para o qual o ConcurrentBag foi projetado.


9
Acho que essa resposta é enganosa. Para ficar claro, isso NÃO fornece nenhuma segurança de rosca na operação de remoção desejada. E colocar um bloqueio em torno dele meio que anula o propósito de usar uma coleção simultânea.
ILIA BROUDNO

1
Concordo. Bem, para esclarecer um pouco, o ConcurrentBag foi projetado para ser preenchido, enumerado e jogado fora com todo o seu conteúdo quando estiver pronto. Quaisquer tentativas - incluindo a minha - de remover um item resultarão em um hack sujo. Pelo menos tentei dar uma resposta, embora o melhor seja usar uma classe de coleção concorrente melhor, como o ConcurrentDictionary.
Larry

4

Mark está correto em que ConcurrentDictionary funcionará da maneira que você deseja. Se você ainda deseja usar um ConcurrentBag, o seguinte, não muito eficiente, irá levá-lo 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

Como você mencionou, TryTake()é a única opção. Este também é o exemplo no MSDN . O refletor também não mostra outros métodos internos ocultos de 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é uma coleção não ordenada, mas seu código espera isso bag.TryTakee bag.Addfunciona de maneira FIFO. Seu código assume que baginclui item, ele faz um loop até encontrar itemem bag. Respostas apenas codificadas são desencorajadas, você deve explicar sua solução.
GDavid

-1

Esta é minha aula de extensão que estou usando em meus projetos. Ele pode remover um único item do ConcurrentBag e também pode remover a lista de itens do saco

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

É difícil acreditar que pode funcionar porque você está criando um novo ConcurrentBag na variável local, mas não tenho certeza. algum teste?
shtse8

3
Não, não funciona. Como deve funcionar, você está criando apenas uma nova referência. O antigo ainda está apontando para o objeto antigo ... Funcionaria se você trabalhar com "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

e quanto a:

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

Funciona, não tenho certeza de quão eficientemente ...


Isso não remove nada da bolsa. O item que você está recuperando permanece dentro da sacola.
Keith

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

4
@atikot, essa frase me fez rir
parek
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.