In our previous article Threading in C# Part1 we have seen multi threading in C# with an example using System.Threading. In this article, we will see what is Task Parallel Library or TPL in C# & we will discuss Parallel For and Parallel ForEach with examples.
Task Parallel Library (TPL)
Starting with .NET 4.0 TPL is the preferred way of writing multi threaded programs. TPL scales the degree of concurrency dynamically to most efficiently use all the processors that are available. Also, it helps the developers to write the code easily to improve concurrency and parallelism. Concurrency or parallelism can be sometimes slower than sequential if the operation is very small.
.NET framework also provides parallel loop implementation where we can run the redundant tasks which are similar and independent of each other. The Parallel for
& foreach
loop looks similar to the normal for loop.
Parallel For & Foreach
In Parallel for loop, it divides the source collections into different partitions so that different partitions can be run parallel. The partitioning of the source is handled by Task Scheduler. Task Scheduler may do partitions based on the amount of work and system resources etc.
Let’s look parallel for loop. There are different implementations for this, we will see some basic examples.
Parallel For
using System; using System.Threading.Tasks; namespace Learn { class Learn { static void Main(string[] args) { //arg1:loop starts from 0 //arg2:loop goes till 999 //arg3:the operation needs to be performed ,here lamda function used Parallel.For(0, 1000, x => { Console.Write(x + ","); }); Console.ReadLine(); } } }
output
Parallel ForEach
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Learn { class Learn { static void Main() { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //arg1: source collection //arg2:task passed as lambda function here. Parallel.ForEach<int>(array, y => { Console.WriteLine(y); }); Console.ReadKey(); } } }
In the output of the above example, it is clear that the loop is not running in sequential order, different partitions run parallel with their own range. So any range can begin executing.
Parallel.For<> with local variables
The below example explains how we can use local variables while using a parallel loop. In the below example the source collection is partitioned and executed as different tasks, each task will be having a part of collections to loop over.
The first two arguments define loop range, third argument initializes the local variables that we want to run tasks. If we have multiple parameters we can use anonymous type to pass multiple initialization values. The fourth argument is the task body where we specify the work needs to be done, we can pass the local variable here, the last argument or fifth argument is a function which will be executed once for every task. Since multiple tasks may be trying to use shared resources we must write code in a thread-safe way.
We can use initialization section to initialize the variables,db connection etc. similarly last argument can be used to do clean up process etc.
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Learn { class Learn { static void Main() { //source collection int[] array = Enumerable.Range(1, 10).ToArray(); long grandTotal = 0; Parallel.For<long>( 0, //start index inclusive 10, //end index exclusive () => 0,//local initialization (j, loop, sumPartition) =>//body here sumPartition=0 for each task. { //this will be executed for all the //range in a partition given to each task. Console.WriteLine("Index={0} on Thread={1}", j, Thread.CurrentThread.ManagedThreadId); sumPartition += array[j]; return sumPartition; }, sT =>//executed for each task, //this will add sum to grandTotal from all the taks. { //executed for each task completion. Interlocked.Add(ref grandTotal, sT); } ); Console.WriteLine("Total={0}", grandTotal); Console.ReadKey(); } } }
We can write the above example using Parallel.ForEach loop
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Learn { class Learn { static void Main() { int[] array = Enumerable.Range(1, 10).ToArray(); long grandTotal = 0; Parallel.ForEach<int, long>(array, () => 0, (j, loop, sumPartition) => { Console.WriteLine("Item={0} on Thread={1}", j, Thread.CurrentThread.ManagedThreadId); sumPartition += j; return sumPartition; }, sT => { Interlocked.Add(ref grandTotal, sT); } ); Console.WriteLine("Total={0}", grandTotal); Console.ReadKey(); } } }