如何将此foreach代码转换为Parallel.ForEach?

210

我有些困惑于Parallel.ForEach
什么是Parallel.ForEach,它到底是做什么的?
请不要引用任何MSDN链接。

以下是一个简单的示例:

string[] lines = File.ReadAllLines(txtProxyListPath.Text);
List<string> list_lines = new List<string>(lines);

foreach (string line in list_lines)
{
    //My Stuff
}

我如何使用Parallel.ForEach重写这个示例?


这个问题可能已经在这里得到了回答:https://dev59.com/92865IYBdhLWcg3wfemU - Ujjwal Manandhar
2
@UjjwalManandhar,这实际上是非常不同的问题,因为它询问了Parallel类和使用PLINQ之间的区别。 - Reed Copsey
19
其他人已回答如何重写代码。那么它的作用是什么呢?它会对集合中的每个项目进行“操作”,就像普通的 foreach 循环一样。不同之处在于并行版本可以同时执行许多“操作”。在大多数情况下(取决于运行代码的计算机、其繁忙程度和其他因素),它将更快,这是最重要的优点。请注意,当您并行处理时,无法知道处理项目的顺序。使用普通(串行)foreach循环,您可以保证会先处理lines[0],然后是lines[1]等。 - Jeppe Stig Nielsen
1
@JeppeStigNielsen,使用并行化不一定总是更快的,因为并行化会带来很大的开销。这取决于您正在迭代的集合的大小以及其中的操作。正确的做法是实际上测量使用Parallel.ForEach()和使用foreach()之间的差异。许多时候,普通的foreach()更快。 - Dave Black
3
@DaveBlack 当然。每种情况都需要“测量”其是否更快或更慢。我只是试图概述并行化的一般性质。 - Jeppe Stig Nielsen
6个回答

310

foreach循环:

  • 迭代按顺序逐个进行
  • foreach循环从单个线程运行。
  • foreach循环在.NET的每个框架中都有定义
  • 执行缓慢进程可能会更慢,因为它们是串行运行的
    • 进程2不能启动,直到1完成。进程3不能启动,直到2和1完成...
  • 执行快速进程可能会更快,因为没有线程开销

Parallel.ForEach:

  • 并行执行。
  • Parallel.ForEach使用多个线程。
  • Parallel.ForEach在.NET 4.0及以上框架中定义。
  • 执行缓慢进程可能会更快,因为它们可以并行运行
    • 进程1、2和3可能同时运行(请参见下面的示例中的重复使用的线程)
  • 执行快速进程可能会更慢,因为有额外的线程开销

以下示例清楚地演示了传统foreach循环和

Parallel.ForEach()示例

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ParallelForEachExample
{
    class Program
    {
        static void Main()
        {
            string[] colors = {
                                  "1. Red",
                                  "2. Green",
                                  "3. Blue",
                                  "4. Yellow",
                                  "5. White",
                                  "6. Black",
                                  "7. Violet",
                                  "8. Brown",
                                  "9. Orange",
                                  "10. Pink"
                              };
            Console.WriteLine("Traditional foreach loop\n");
            //start the stopwatch for "for" loop
            var sw = Stopwatch.StartNew();
            foreach (string color in colors)
            {
                Console.WriteLine("{0}, Thread Id= {1}", color, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(10);
            }
            Console.WriteLine("foreach loop execution time = {0} seconds\n", sw.Elapsed.TotalSeconds);
            Console.WriteLine("Using Parallel.ForEach");
            //start the stopwatch for "Parallel.ForEach"
             sw = Stopwatch.StartNew();
            Parallel.ForEach(colors, color =>
            {
                Console.WriteLine("{0}, Thread Id= {1}", color, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(10);
            }
            );
            Console.WriteLine("Parallel.ForEach() execution time = {0} seconds", sw.Elapsed.TotalSeconds);
            Console.Read();
        }
    }
}

输出

Traditional foreach loop
1. Red, Thread Id= 10
2. Green, Thread Id= 10
3. Blue, Thread Id= 10
4. Yellow, Thread Id= 10
5. White, Thread Id= 10
6. Black, Thread Id= 10
7. Violet, Thread Id= 10
8. Brown, Thread Id= 10
9. Orange, Thread Id= 10
10. Pink, Thread Id= 10
foreach loop execution time = 0.1054376 seconds

使用Parallel.ForEach示例

1. Red, Thread Id= 10
3. Blue, Thread Id= 11
4. Yellow, Thread Id= 11
2. Green, Thread Id= 10
5. White, Thread Id= 12
7. Violet, Thread Id= 14
9. Orange, Thread Id= 13
6. Black, Thread Id= 11
8. Brown, Thread Id= 10
10. Pink, Thread Id= 12
Parallel.ForEach() execution time = 0.055976 seconds

67
我不完全同意你的“断言”,即Parallel.ForEach(始终)更快。这取决于循环内部操作的复杂程度,可能并不值得引入并行处理的开销。 - Martao
3
@Jignesh,这个示例并不是一个好的测量例子,所以我不会参考它。从每个循环体中删除“Thread.Sleep(10);”,然后再试一次。 - st35ly
1
@Martao是正确的,问题在于对象锁定开销,其中并行方法可能比顺序方法更长。 - st35ly
8
@stenly,我认为“睡眠”正是为什么它是一个“好”例子的原因。你不会在快速单次迭代的情况下使用PFE(如Martao所解释的),因此这个答案使迭代变慢,并突出了PFE的(正确)优点。尽管如此,我同意需要在答案中对此进行解释,“始终更快”这样的强调非常具有误导性。 - mafu
1
@Highmastdon - 请阅读一下并了解何时并行处理可能会变慢。http://blogs.msdn.com/b/pfxteam/archive/2009/06/06/9703059.aspx - Dave Black
显示剩余6条评论

140
string[] lines = File.ReadAllLines(txtProxyListPath.Text);
List<string> list_lines = new List<string>(lines);
Parallel.ForEach(list_lines, line =>
{
    //Your stuff
});

6
只是想指出一下(更多是针对OP),以免有误解认为它只适用于List<T> ;) - Reed Copsey
1
感谢关注和回答。因为要使用HASH列表来删除重复项,所以我在我的代码中使用了List<string>。使用常规数组无法轻松地删除重复项:)。 - SilverLight
126
我很困惑为什么这个答案被标记为正确答案,因为它没有解释原帖子的问题:“Parallel.ForEach是什么,它到底是做什么的?”… - fose
6
问题在于问题标题被编辑以完全改变其含义...因此,这个回答不再有意义。话虽如此,它仍然是一个糟糕的回答。 - aw04

47
string[] lines = File.ReadAllLines(txtProxyListPath.Text);

// No need for the list
// List<string> list_lines = new List<string>(lines); 

Parallel.ForEach(lines, line =>
{
    //My Stuff
});
这将导致在循环内并行解析这些行。如果你想获得更详细、不那么“参考导向”的Parallel类介绍,我写了一系列关于TPL的文章,其中包括一个关于Parallel.ForEach的部分

11

对于大文件,请使用以下代码(可减少内存占用)

Parallel.ForEach(File.ReadLines(txtProxyListPath.Text), line => {
    //Your stuff
});

5
这些代码对我有用。
string[] lines = File.ReadAllLines(txtProxyListPath.Text);
var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 10 };
Parallel.ForEach(lines , options, (item) =>
{
 //My Stuff
});

2

我想补充一下关于并行选项的内容。如果您没有提到它,默认情况下所有的RAM都将用于此,这可能会在生产中给您带来问题。因此最好在代码中添加最大并行度。

Parallel.ForEach(list_lines, new ParallelOptions { MaxDegreeOfParallelism = 2 }, line =>
{
    
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接