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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12610868/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 23:53:56  来源:igfitidea点击:

How do I collect return values from Parallel.ForEach?

c#c#-4.0parallel.foreach

提问by MatthewMartin

I'm calling a slow webservice in parallel. Things were great until I realized I need to get some information back from the service. But I don't see where to get the values back. I can't write to the database, HttpContext.Current appears to be null inside of a method called using Parallel.ForEach

我正在并行调用一个缓慢的网络服务。一切都很好,直到我意识到我需要从服务中获取一些信息。但我不知道从哪里取回这些值。我无法写入数据库,HttpContext.Current 在使用 Parallel.ForEach 调用的方法中似乎为空

Below is a sample program (in your mind, please imagine a slow web service instead of a string concatenation)

下面是一个示例程序(在您的脑海中,请想象一个缓慢的 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;
        }
    }

}

采纳答案by M Afifi

You've discarded it in here.

你已经把它扔在这里了。

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

You probably want something like,

你可能想要这样的东西,

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

If you want some sort of collection at the end of this, consider using one of the collections under System.Collections.Concurrent, like ConcurrentBag

如果您希望在此末尾进行某种集合,请考虑使用 下的集合之一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

回答by Minh Nguyen

Do not use ConcurrentBagto collect results as it is extremely slow. Use local lock instead.

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

回答by MichaC

How about something like this:

这样的事情怎么样:

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

I believe the locking or concurrent objects isn't necessary unless you need the results to interact with one another (like you were computing a sum or combining all the words). In this case ForEach neatly breaks your original list up and hands each thread its own object that it can manipulate all it wants without worrying about interfering with the other threads.

我相信锁定或并发对象不是必需的,除非您需要结果彼此交互(就像您正在计算总和或组合所有单词)。在这种情况下, ForEach 巧妙地分解了您的原始列表,并为每个线程提供了自己的对象,它可以操纵它想要的所有对象,而不必担心干扰其他线程。

回答by Steves

Your may consider using AsParallelextension method of IEnumerable, it will take care of the concurrency for you and collect the results.

您可以考虑使用 的AsParallel扩展方法IEnumerable,它会为您处理并发并收集结果。

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

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

Synchronisation (e.g. locks or concurrent collections that use locks) are usually bottleneck of concurrent algorithms. The best is to avoid synchronisation as much as possible. I am guessing that AsParalleluses something smarter like putting all the items produced on single thread into a local non-concurrent collection and then combining these at the end.

同步(例如锁或使用锁的并发集合)通常是并发算法的瓶颈。最好是尽可能避免同步。我猜想AsParallel使用更智能的方法,例如将单线程上生成的所有项目放入本地非并发集合中,然后在最后将它们组合起来。

回答by Overlord Zurg

This seems safe, fast, and 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;
    }