如何从Parallel.ForEach收集返回值?

时间:2012-09-26 21:39:58

标签: c# c#-4.0 parallel.foreach

我正在并行调用一个缓慢的Web服务。事情很棒,直到我意识到我需要从服务中获取一些信息。但我不知道在哪里可以获得价值。我无法写入数据库,HttpContext.Current似乎在使用Parallel.ForEach调用的方法中为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;
        }
    }

}

5 个答案:

答案 0 :(得分:60)

你已经放弃了它。

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

你可能想要类似的东西,

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

如果您想在此结束时使用某种类型的集合,请考虑使用System.Collections.Concurrent下的其中一个集合,例如ConcurrentBag

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

// Do something with the result

答案 1 :(得分:22)

您可以考虑使用AsParallel的{​​{1}}扩展方法,它将为您处理并发并收集结果。

IEnumerable

同步(例如,使用锁的锁或并发集合)通常是并发算法的瓶颈。最好的方法是尽可能避免同步。我猜测 words.AsParallel().Select(AddB).ToArray() 使用更聪明的东西,例如将在单个线程上生成的所有项目放入本地非并发集合中,然后在最后组合这些项目。

答案 2 :(得分:11)

请勿使用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

答案 3 :(得分:3)

这样的事情怎么样:

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巧妙地打破了你的原始列表并将每个线程交给它自己的对象,它可以操纵它想要的所有内容,而不必担心干扰其他线程。

答案 4 :(得分:1)

这似乎安全,快速,简单:

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