web-dev-qa-db-fra.com

Comment supprimer un seul objet spécifique d'un ConcurrentBag <>?

Avec le nouveau ConcurrentBag<T> dans .NET 4, comment enlevez-vous un certain objet spécifique lorsque seuls TryTake() et TryPeek() sont disponibles?

Je songe à utiliser TryTake(), puis à rajouter simplement l'objet résultant dans la liste si je ne pas veux le supprimer, mais j'ai le sentiment que quelque chose me manque. Est-ce la bonne façon?

84
Brandon

La réponse courte: vous ne pouvez pas le faire facilement.

Le ConcurrentBag conserve une file d'attente locale de threads pour chaque thread et il ne regarde que les files d'attente des autres threads une fois que sa propre file d'attente est vide. Si vous supprimez un élément et le remettez en place, l'élément suivant que vous supprimez peut être à nouveau le même. Rien ne garantit que le fait de supprimer et de remettre à plusieurs reprises des éléments vous permettra de parcourir tous les éléments.

Deux alternatives pour vous:

  • Supprimez tous les éléments et rappelez-vous-les jusqu'à ce que vous trouviez celui que vous souhaitez supprimer, puis remettez les autres par la suite. Notez que si deux threads essayent de faire cela simultanément, vous aurez des problèmes.
  • Utilisez une structure de données plus appropriée telle que ConcurrentDictionary .
71
Mark Byers

Tu ne peux pas. C'est un sac, il n'est pas commandé. Lorsque vous le remettez, vous restez coincé dans une boucle sans fin.

Vous voulez un ensemble. Vous pouvez en émuler un avec ConcurrentDictionary. Ou un HashSet que vous vous protégez avec un verrou.

15
Hans Passant

Le ConcurrentBag est idéal pour gérer une liste dans laquelle vous pouvez ajouter des éléments et énumérer de nombreux threads, puis éventuellement le jeter comme son nom l'indique :)

Comme Mark Byers l'a dit , vous pouvez reconstruire un nouveau ConcurrentBag qui ne contient pas l'élément que vous souhaitez supprimer, mais vous devez le protéger contre plusieurs hits de threads à l'aide d'un verrou. Ceci est un one-liner:

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

Cela fonctionne et correspond à l'esprit dans lequel le ConcurrentBag a été conçu.

4
Larry

Comme vous le mentionnez, TryTake() est la seule option. C'est également l'exemple sur MSDN . Le réflecteur ne montre aucune autre méthode d’intérêt interne cachée.

3
Mikael Svenson

Mark est correct car le ConcurrentDictionary is fonctionnera comme vous le souhaitez. Si vous souhaitez toujours utiliser un ConcurrentBag ce qui suit, pas très efficace, vous y arrivera.

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

}
1
huda

Essayez BlockingCollection<T> à la place. Disponible en .Net Core.

0
Chris Bordeman

Ceci est ma classe d'extension que j'utilise dans mes projets. Il peut supprimer un seul élément de ConcurrentBag et peut également supprimer une liste d’articles du sac

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