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

我正在调用一个缓慢的web服务并行。 事情很棒,直到我意识到我需要从服务中获得一些信息。 但是我不知道从哪里得到价值。 我不能写入数据库,HttpContext.Current似乎是一个使用Parallel.ForEach调用的方法

下面是一个示例程序(在你的脑海中,请想象一个缓慢的Web服务,而不是string连接)

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

你已经在这里丢弃了。

 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

 var resultCollection = new ConcurrentBag<string>(); ParallelLoopResult result = Parallel.ForEach(words, word => { resultCollectin.Add(AddB(word)); }); // Do something with result 

不要使用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 

你可以考虑使用IEnumerable AsParallel扩展方法,它会考虑你的并发性并收集结果。

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

同步(例如使用锁的锁或并发集合)通常是并发algorithm的瓶颈。 最好的是尽可能地避免同步。 我猜测, AsParallel使用了一些更聪明的方法,比如把所有在单线程上生成的项目放到一个本地的非并发集合中,然后把它们结合起来。

怎么样这样的事情:

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

我相信,locking或并发对象是没有必要的,除非你需要结果互相交stream(如你正在计算一笔金额或合并所有的单词)。 在这种情况下,ForEach整齐地打破你的原始列表,并且把每个线程移动到它自己的对象,它可以操纵所有它想要的,而不用担心干扰其他线程。

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

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