web-dev-qa-db-fra.com

Comment collecter les valeurs de retour de Parallel.ForEach?

J'appelle un webservice lent en parallèle. Les choses se sont bien passées jusqu'à ce que je réalise que j'ai besoin de récupérer des informations du service. Mais je ne vois pas où récupérer les valeurs. Je ne peux pas écrire dans la base de données, HttpContext.Current semble être nul à l'intérieur d'une méthode appelée à l'aide de Parallel.ForEach

Voici un exemple de programme (dans votre esprit, imaginez un service Web lent au lieu d'une concaténation de chaînes)

using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        WordMaker m = new WordMaker();
        m.MakeIt();
    }
    public class WordMaker
    {
        public void MakeIt()
        {
            string[] words = { "ack", "ook" };
            ParallelLoopResult result = Parallel.ForEach(words, Word => AddB(Word));
            Console.WriteLine("Where did my results go?");
            Console.ReadKey();
        }
        public string AddB(string Word)
        {
            return "b" + Word;
        }
    }

}
52
MatthewMartin

Vous l'avez jeté ici.

ParallelLoopResult result = Parallel.ForEach(words, Word => AddB(Word));

Vous voulez probablement quelque chose comme,

ParallelLoopResult result = Parallel.ForEach(words, Word =>
{
    string result = AddB(Word);
    // do something with result
});

Si vous voulez une sorte de collection à la fin de cela, pensez à utiliser l'une des collections sous System.Collections.Concurrent, comme ConcurrentBag

ConcurrentBag<string> resultCollection = new ConcurrentBag<string>();
ParallelLoopResult result = Parallel.ForEach(words, Word =>
{
    resultCollection.Add(AddB(Word));
});

// Do something with the result
61
M Afifi

Vous pouvez envisager d'utiliser la méthode d'extension AsParallel de IEnumerable, elle s'occupera de la concurrence pour vous et collectera les résultats.

words.AsParallel().Select(AddB).ToArray()

La synchronisation (par exemple des verrous ou des collections simultanées qui utilisent des verrous) est généralement un goulot d'étranglement des algorithmes simultanés. Le mieux est d'éviter autant que possible la synchronisation. Je suppose que AsParallel utilise quelque chose de plus intelligent comme mettre tous les éléments produits sur un seul thread dans une collection locale non simultanée, puis les combiner à la fin.

25
Steves

N'utilisez pas ConcurrentBag pour collecter les résultats car il est extrêmement lent. Utilisez plutôt un verrou local.

var resultCollection = new List<string>();
object localLockObject = new object();

Parallel.ForEach<string, List<string>>(
      words,
      () => { return new List<string>(); },
      (Word, state, localList) =>
      {
         localList.Add(AddB(Word));
         return localList;
      },
      (finalResult) => { lock (localLockObject) resultCollection.AddRange(finalResult); }
); 

// Do something with resultCollection here
11
Minh Nguyen

Que diriez-vous quelque chose comme ça:

public class WordContainer
{
    public WordContainer(string Word)
    {
        Word = Word;
    }

    public string Word { get; private set; }
    public string Result { get; set; }
}

public class WordMaker
{
    public void MakeIt()
    {
        string[] words = { "ack", "ook" };
        List<WordContainer> containers = words.Select(w => new WordContainer(w)).ToList();

        Parallel.ForEach(containers, AddB);

        //containers.ForEach(c => Console.WriteLine(c.Result));
        foreach (var container in containers)
        {
            Console.WriteLine(container.Result);
        }

        Console.ReadKey();
    }

    public void AddB(WordContainer container)
    {
        container.Result = "b" + container.Word;
    }
}

Je pense que le verrouillage ou les objets simultanés ne sont pas nécessaires sauf si vous avez besoin des résultats pour interagir les uns avec les autres (comme si vous calculiez une somme ou combiniez tous les mots). Dans ce cas, ForEach décompose soigneusement votre liste d'origine et remet à chaque thread son propre objet qu'il peut manipuler tout ce qu'il veut sans se soucier d'interférer avec les autres threads.

3
MichaC

Cela semble sûr, rapide et simple:

    public string[] MakeIt() {
        string[] words = { "ack", "ook" };
        string[] results = new string[words.Length];
        ParallelLoopResult result =
            Parallel.For(0, words.Length, i => results[i] = AddB(words[i]));
        return results;
    }
1
Overlord Zurg