A while ago, during a work retreat (yes, the company I work for is really that cool), Graham Rook, who is one of my colleagues, showed me a parallel foreach
loop. It certainly speeds up processing a great deal. But here's the rub. It makes no sense using a parallel foreach
loop if you're dealing with small amounts of data or little tasks. The parallel foreach
loop excels when there is bulk processing to do or huge amounts of data to process.
We will start off by looking at where the parallel foreach
loop does not perform better than the standard foreach
loop. For this, we will create a small list of 500 items and just iterate over the list, writing the items to the console window.
For the second example that illustrates the power of the parallel foreach
loop, we will use the same list and create a file for each item in the list. The power and benefit of the parallel foreach
loop will be evident in the second example.
Recipes
class. Call one method ReadCollectionForEach()
and pass it a parameter of List<string>
. Create a second method called ReadCollectionParallelForEach()
that also accepts a parameter of List<string>
:public class Recipes { public double ReadCollectionForEach(List<string> intCollection) { } private double ReadCollectionParallelForEach(List<string> intCollection) { } }
ReadCollectionForEach()
method, add a standard foreach
loop that will iterate over the collection of strings passed to it and write the value it finds to the console window. Then, clear the console window. Use a timer to keep track of the total seconds elapsed during the foreach
loop:var timer = Stopwatch.StartNew(); foreach (string integer in intCollection) { WriteLine(integer); Clear(); } return timer.Elapsed.TotalSeconds;
ReadCollectionParallelForEach()
, do the same. However, instead of using a standard foreach
loop, add a Parallel.ForEach
loop. You will notice that the Parallel.ForEach
loop looks slightly different. The signature of Parallel.ForEach
requires that you pass it an enumerable data source (List<string> intCollection
) and define an action, which is the delegate that is invoked for every iteration (integer
):var timer = Stopwatch.StartNew(); Parallel.ForEach(intCollection, integer => { WriteLine(integer); Clear(); }); return timer.Elapsed.TotalSeconds;
Recipes
class should look like this:public class Recipes { public double ReadCollectionForEach(List<string> intCollection) { var timer = Stopwatch.StartNew(); foreach (string integer in intCollection) { WriteLine(integer); Clear(); } return timer.Elapsed.TotalSeconds; } public double ReadCollectionParallelForEach(List<string> intCollection) { var timer = Stopwatch.StartNew(); Parallel.ForEach(intCollection, integer => { WriteLine(integer); Clear(); }); return timer.Elapsed.TotalSeconds; } }
List<string>
collection and pass it to the two methods created in the Recipes
class. You will notice that we are only creating a collection of 500 items. After the code is completed, return the time elapsed in seconds and output it to the console window:List<string> integerList = new List<string>(); for (int i = 0; i <= 500; i++) { integerList.Add(i.ToString()); } Chapter7.Recipes oRecipe = new Chapter7.Recipes(); double timeElapsed1 = oRecipe.ReadCollectionForEach(integerList); double timeElapsed2 = oRecipe.ReadCollectionParallelForEach(integerList); WriteLine($"foreach executed in {timeElapsed1}"); WriteLine($"Parallel.ForEach executed in {timeElapsed2}");
Parallel.ForEach
loop is negligible. In fact, in this case, the Parallel.ForEach
loop only improved performance by 0.4516 percent:Parallel.ForEach
loop will give us. Create two methods called CreateWriteFilesForEach()
and CreateWriteFilesParallelForEach()
, which both take the List<string>
collection as the parameter:public class Recipes { public void CreateWriteFilesForEach(List<string> intCollection) { } private void CreateWriteFilesParallelForEach(List<string> intCollection) { } }
CreateWriteFilesForEach()
method. This code starts the timer and executes the standard foreach
loop on the List<string>
object. It then writes the elapsed time out to the console window:WriteLine($"Start foreach File method"); var timer = Stopwatch.StartNew(); foreach (string integer in intCollection) { } WriteLine($"foreach File method executed in {timer.Elapsed.TotalSeconds} seconds");
foreach
loop, add the code to check whether a file exists with the specific name created by appending the integer
value to the filename portion of the filePath
variable. Create the file (ensuring that you use Dispose
method in order not to lock the file when trying to write to it) and write some text to the newly created file:string filePath = $"C:\temp\output\ForEach_Log{integer}.txt"; if (!File.Exists(filePath)) { File.Create(filePath).Dispose(); using (StreamWriter sw = new StreamWriter(filePath, false)) { sw.WriteLine($"{integer}. Log file start: {DateTime.Now.ToUniversalTime().ToString()}"); } }
CreateWriteFilesParallelForEach()
method, which basically performs the same function as the CreateWriteFilesForEach()
method, but uses a Parallel.ForEach
loop to create and write files:WriteLine($"Start Parallel.ForEach File method"); var timer = Stopwatch.StartNew(); Parallel.ForEach(intCollection, integer => { }); WriteLine($"Parallel.ForEach File method executed in {timer.Elapsed.TotalSeconds} seconds");
Parallel.ForEach
loop:string filePath = $"C:\temp\output\ParallelForEach_Log{integer}.txt"; if (!File.Exists(filePath)) { File.Create(filePath).Dispose(); using (StreamWriter sw = new StreamWriter(filePath, false)) { sw.WriteLine($"{integer}. Log file start: {DateTime.Now.ToUniversalTime().ToString()}"); } }
public class Recipes { public void CreateWriteFilesForEach(List<string> intCollection) { WriteLine($"Start foreach File method"); var timer = Stopwatch.StartNew(); foreach (string integer in intCollection) { string filePath = $"C:\temp\output\ForEach_Log{integer}.txt"; if (!File.Exists(filePath)) { File.Create(filePath).Dispose(); using (StreamWriter sw = new StreamWriter(filePath, false)) { sw.WriteLine($"{integer}. Log file start: {DateTime.Now.ToUniversalTime() .ToString()}"); } } } WriteLine($"foreach File method executed in {timer.Elapsed.TotalSeconds} seconds"); } public void CreateWriteFilesParallelForEach(List<string> intCollection) { WriteLine($"Start Parallel.ForEach File method"); var timer = Stopwatch.StartNew(); Parallel.ForEach(intCollection, integer => { string filePath = $"C:\temp\output\ParallelForEach_Log {integer}.txt"; if (!File.Exists(filePath)) { File.Create(filePath).Dispose(); using (StreamWriter sw = new StreamWriter(filePath, false)) { sw.WriteLine($"{integer}. Log file start: {DateTime.Now.ToUniversalTime() .ToString()}"); } } }); WriteLine($"Parallel.ForEach File method executed in {timer.Elapsed.TotalSeconds} seconds"); } }
List<string>
object slightly and increase the count from 500
to 1000
. Then, call the file methods created in the Recipes
class:List<string> integerList = new List<string>(); for (int i = 0; i <= 1000; i++) { integerList.Add(i.ToString()); } Chapter7.Recipes oRecipe = new Chapter7.Recipes(); oRecipe.CreateWriteFilesForEach(integerList); oRecipe.CreateWriteFilesParallelForEach(integerList); ReadLine();
C: empoutput
directory and that there aren't any other files in that directory. Run your application and review the output to the console window. This time round, we can see that the Parallel.ForEach
loop has made a huge difference. The performance gain is massive and heralds a 60.7074 percent performance increase over the standard foreach
loop:From the examples used in this recipe, it is clear that the use of the parallel foreach
loop should be considered carefully. If you are dealing with relatively low volumes of data or non-process intensive transactions, the parallel foreach
loop will not benefit your application's performance much. In some instances, the standard foreach
loop could be much faster than the parallel foreach
loop. If, however, you find your application running into performance issues when processing large amounts of data or running processor-intensive tasks, give the parallel foreach
loop a try. It just might surprise you.
18.117.189.228