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


109

Avec le nouveau ConcurrentBag<T>dans 4 .NET, comment vous supprimez un certain objet spécifique de celui - ci quand seulement TryTake()et TryPeek()sont disponibles?

Je pense utiliser TryTake()puis simplement ajouter l'objet résultant dans la liste si je ne veux pas le supprimer, mais j'ai l'impression qu'il me manque peut-être quelque chose. C'est la bonne route?

Réponses:


89

La réponse courte: vous ne pouvez pas le faire de manière simple.

Le ConcurrentBag conserve une file d'attente locale pour chaque thread et il ne regarde que les files 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. Il n'y a aucune garantie que la suppression répétée d'éléments et leur remise en place vous permettront d'itérer sur tous les éléments.

Deux alternatives pour vous:

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

9
SynchronizedCollection peut également être un substitut approprié.
ILIA BROUDNO

2
@ILIABROUDNO - vous devriez mettre cela comme réponse! C'est BEAUCOUP mieux qu'un kludgey ConcurrentDictionary quand vous n'avez pas besoin d'un dictionnaire
Denis

2
Pour info, SynchronizedCollection n'est pas disponible dans .NET Core. À la date de ce commentaire, les types System.Collections.Concurrent sont la voie actuelle pour les implémentations basées sur .NET Core.
Matthew Snyder

2
Je ne sais pas quelle version de .NET Core était utilisée, mais je travaille sur un projet basé sur le SDK .NET Core 2.1 et SynchronizedCollection est désormais disponible dans l'espace de noms Collections.Generic.
Lucas Leblanc

15

Vous ne pouvez pas. C'est un sac, il n'est pas commandé. Lorsque vous le remettrez, vous resterez 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.


8
Veuillez développer. Que utiliseriez-vous comme clé dans le ConcurrentDictionary sous-jacent?
Denise Skidmore

2
Eh bien, je suppose que la clé serait le type de l'objet que vous essayez de stocker, puis la valeur serait une collection de certaines sortes. Cela "imiterait" un HashSetcomme il le décrit.
Mathias Lykkegaard Lorenzen

5

Le ConcurrentBag est idéal pour gérer une liste où vous pouvez ajouter des éléments et énumérer à partir de nombreux threads, puis finalement 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 les hits de plusieurs threads à l'aide d'un verrou. C'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.


9
Je pense que cette réponse est trompeuse. Pour être clair, cela ne fournit AUCUNE sécurité de filetage dans l'opération de suppression souhaitée. Et mettre un verrou autour de lui va un peu à l'encontre de l'objectif d'utiliser une collection simultanée.
ILIA BROUDNO

1
Je suis d'accord. Eh bien, pour clarifier un peu, le ConcurrentBag est conçu pour être rempli, énuméré et jeté avec tout son contenu une fois terminé. Toute tentative - y compris la mienne - de supprimer un élément entraînera un piratage malveillant. Au moins, j'ai essayé de fournir une réponse, même si le mieux est d'utiliser une meilleure classe de collecte simultanée, comme le ConcurrentDictionary.
Larry

4

Mark a raison en ce que le ConcurrentDictionary fonctionnera comme vous le souhaitez. Si vous souhaitez continuer à utiliser un ConcurrentBag , ce qui n'est pas efficace vous y conduira.

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

Comme vous le mentionnez, TryTake()c'est la seule option. C'est également l'exemple sur MSDN . Reflector ne montre pas non plus d'autres méthodes internes cachées d'intérêt.


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

}

ConcurrentBagest une collection non ordonnée, mais votre code s'y attend bag.TryTakeet bag.Addfonctionne de manière FIFO. Votre code suppose que bagcomprend item, il boucle jusqu'à ce qu'il trouve itemdans bag. Les réponses au code uniquement sont déconseillées, vous devez expliquer votre solution.
GDavid

-1

C'est ma classe d'extension que j'utilise dans mes projets. Il peut supprimer un seul article de ConcurrentBag et peut également supprimer la liste des 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);
        }
    }
}

Il est difficile de croire que cela peut fonctionner car vous créez un nouveau ConcurrentBag dans une variable locale, mais je ne suis pas sûr. un test?
shtse8

3
Non, ça ne marche pas. Comment cela devrait-il fonctionner, vous créez une seule nouvelle référence. L'ancien pointe toujours vers l'ancien objet ... Cela fonctionnerait si vous travaillez avec "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

que diriez-vous:

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

Cela fonctionne, je ne sais pas avec quelle efficacité ...


Cela n'enlève rien du sac. L'article que vous récupérez reste à l'intérieur du sac.
Keith

3
devrait être "bag = new ConcurrentBag (bag.Where (x => x! = item))"
atikot

4
@atikot, cette ligne m'a fait rire
parek
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.