web-dev-qa-db-fra.com

ConcurrentBag - Ajouter plusieurs éléments?

Existe-t-il un moyen d'ajouter plusieurs éléments à ConcurrentBag à la fois, au lieu d'un à la fois? Je ne vois pas de méthode AddRange () sur ConcurrentBag, mais il y a un Concat (). Cependant, cela ne fonctionne pas pour moi:

ConcurrentBag<T> objectList = new ConcurrentBag<T>();

timeChunks.ForEach(timeChunk =>
{
    List<T> newList = Foo.SomeMethod<T>(x => x.SomeReadTime > timeChunk.StartTime);
    objectList.Concat<T>(newList);
});

Ce code était dans un Parallel.ForEach (), mais je l'ai changé pour ce qui précède afin que je puisse le dépanner. La variable newList contient en effet des objets, mais après la ligne objectList.Concat <>, objectList contient toujours 0 objet. Concat <> ne fonctionne-t-il pas ainsi? Dois-je ajouter des éléments à ConcurrentBag un à la fois, avec la méthode Add ()?

36
Bob Horn

Oui :)

Concat est peut-être l'une des extensions Enumerable. Il n'ajoute rien au ConcurrentBag, il retourne juste un objet funky contenant le sac d'origine et tout ce que vous avez essayé d'y ajouter.

Attention, le résultat de Concat n'est plus un ConcurrentBag, donc vous ne voudriez pas l'utiliser. Cela fait partie du cadre général de LINQ, permettant de combiner des séquences immuables. Ce cadre, bien sûr, n'essaye pas d'étendre les propriétés simultanées des opérandes au résultat, donc l'objet résultant ne sera pas aussi bien adapté à un accès multithread.

(Fondamentalement, Concat s'applique à ConcurrentBag car il expose IEnumerable<T> interface.)

2
Vlad

(Je sais que c'est un vieux post, j'ai pensé ajouter un petit quelque chose).

Comme d'autres l'ont dit: oui, vous devez les ajouter un par un. Dans mon cas, j'ai ajouté une petite méthode d'extension pour rendre les choses un peu plus propres, mais sous le capot, cela fait la même chose:

    public static void AddRange<T>(this ConcurrentBag<T> @this, IEnumerable<T> toAdd)
    {
        foreach (var element in toAdd)
        {
            @this.Add(element);
        }
    }

Puis:

    ConcurrentBag<int> ccBag = new ConcurrentBag<int>();
    var listOfThings = new List<int>() { 1, 2, 4, 5, 6, 7, 8, 9 };
    ccBag.AddRange(listOfThings);

J'ai également envisagé d'utiliser AsParallel pour ajouter dans la méthode d'extension, mais après avoir exécuté des tests sur l'ajout d'une liste de chaînes de différentes tailles, il était toujours plus lent d'utiliser AsParallel (comme indiqué ici) par opposition à la boucle for traditionnelle.

    public static void AddRange<T>(this ConcurrentBag<T> @this, IEnumerable<T> toAdd)
    {
        toAdd.AsParallel().ForAll(t => @this.Add(t));
    }
41
Eric

Concat est une méthode d'extension fournie par LINQ. Il s'agit d'une opération immuable qui renvoie un autre IEnumerable qui peut énumérer la collection source suivie immédiatement par la collection spécifiée. Cela ne change en rien la collection source.

Vous devrez ajouter vos articles à la ConcurrentBag une à la fois.

20
Brian Gideon

J'ai rencontré un problème similaire, en essayant de traiter de plus petits morceaux de données en parallèle, car un gros morceau expirait le service Web que j'utilisais pour accéder à mes données du côté de l'envoi, mais je ne voulais pas que les choses se déroulent plus lentement en traitant chaque morceau en série. Le traitement des données enregistrement par enregistrement a été encore plus lent - étant donné que le service que j'appelais pouvait traiter les demandes en masse, il serait préférable de soumettre autant de données que possible sans expirer.

Comme Vlad l'a dit, concaténer un sac simultané dans une liste d'un type d'objet ne renvoie pas un sac simultané, donc la concaténation ne fonctionnera pas! (Il m'a fallu un certain temps pour réaliser que je ne pouvais pas faire ça.)

Essayez ceci à la place - créez un List<T>, puis créez un ConcurrentBag<List<T>>. À chaque itération parallèle, il ajoutera une nouvelle liste au sac simultané. Lorsque la boucle parallèle est terminée, parcourez ConcurrentBag et concat (ou l'union si vous voulez éliminer les doublons possibles) vers le premier List<T> que vous avez créé pour "aplatir" tout dans une seule liste.

6
Peter Graening

La méthode Concat est une approche contenue dans public Enumerable classe statique qui supporte = System.Linq bibliothèque (interne .NET System.Core Assemblage).

MAIS, le Concat contient des limites pour fournir l'exigence "ajouter une plage" à ConcurrentBag<T> objet, avec un comportement comme image ci-dessous (à la ligne 47 dans Visual Studio):

enter image description here

Pour que la méthode Concat corresponde à l'exigence "ajouter une plage", il est nécessaire de renouveler l'actuel ConcurrentBag<T> instance d'objet; si le programme a besoin d'ajouter plusieurs plages, il est nécessaire de créer des instances de référence automatique à partir du courant ConcurrentBag<T> (récursivement) pour chaque plage.

enter image description here

Ensuite, je n'utilise pas l'approche Concat et si je peux faire une recommandation, JE NE RECOMMANDE PAS. Je suis un exemple similaire de Eric réponse, où j'ai développé une classe dérivée de ConcurrentBag<T> qui me fournit la méthode AddRange en utilisant ConcurrentBag<T> méthode de base pour ajouter IEnumerable<T> éléments de l'instance dérivée, comme ci-dessous:

public class ConcurrentBagCompleted<T> : ConcurrentBag<T>
{
    public ConcurrentBagCompleted() : base() { }

    public ConcurrentBagCompleted(IEnumerable<T> collection):base(collection)
    {
    }

    public void AddRange(IEnumerable<T> collection)
    {
        Parallel.ForEach(collection, item =>
        {
            base.Add(item);
        });
    }
}
0
Antonio Leonardo