web-dev-qa-db-ja.com

Parallel.ForEachから戻り値を収集するにはどうすればよいですか?

遅いWebサービスを並行して呼び出しています。サービスから情報を取得する必要があることに気付くまで、物事は素晴らしかった。しかし、値を取得する場所がわかりません。データベースに書き込むことができません。Parallel.ForEachを使用して呼び出されたメソッド内でHttpContext.Currentがnullのように見えます

以下にサンプルプログラムを示します(心の中で、文字列の連結ではなく、遅いWebサービスを想像してください)

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

ここで破棄しました。

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

おそらくあなたは

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

この最後に何らかのコレクションが必要な場合は、 ConcurrentBag のようなSystem.Collections.Concurrentの下のコレクションのいずれかを使用することを検討してください。

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

// Do something with the result
61
M Afifi

AsParallelIEnumerable拡張メソッドの使用を検討することもできます。これにより、同時実行性が考慮され、結果が収集されます。

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

同期(ロックまたはロックを使用する並行コレクションなど)は、通常、並行アルゴリズムのボトルネックです。最善の方法は、可能な限り同期を避けることです。 AsParallelは、シングルスレッドで生成されたすべてのアイテムをローカルの非コンカレントコレクションに配置し、それらを最後に結合するなど、よりスマートなものを使用していると推測しています。

25
Steves

非常に遅いため、結果の収集にConcurrentBagを使用しないでください。代わりにローカルロックを使用してください。

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

このようなものはどうですか:

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

結果を互いにやり取りする必要がない限り、ロックオブジェクトまたは並行オブジェクトは必要ないと思います(合計を計算したり、すべての単語を組み合わせたりした場合など)。この場合、ForEachは元のリストをきれいに分割し、各スレッドに独自のオブジェクトを渡し、他のスレッドとの干渉を心配することなく必要なすべてを操作できます。

3
MichaC

これは安全、高速、簡単に思えます:

    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