Click here to Skip to main content
Click here to Skip to main content

A Comprehensive Look at the Task Parallel Library

, 18 Nov 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
An article that examines the TPL, Parallel Loop Constructs, and PLINQ

Preface: A Preface to Microsoft’s Concept of a Task

Sometimes when learning new APIs and the corresponding changes in the technology that implements them, it is easier to understand by learning why the new approach has come, rather than how. How it works normally comes by understanding the preceding technology. This article is meant for those who want to learn about the generalities of the Task Parallel Library. An excellent source of reference on these constructs is Jeffrey Richter’s book “The CLR via C#, 3rd Edition. I have referenced some of that material as a baseline for this article.

Abstract

The sum of the parts that make the whole are called Parallel Extensions to the .NET Framework. This includes a collection of task-oriented APIs called the TPL. The unit of concurrency in the TPL is a task object. Coupled with this collection is a data parallel implementation of LINQ called PLINQ. Following is a collection of synchronization primitives that encapsulate coordination patterns. This is a natural following. There is one CLR per process. A process normally has multiple threads running within it. Not only must they execute atomically, they must also access resources in an orderly fashion. The process will access those resources and create a handle table that contains a reference handle id number to enable the thread to recognize the resource. The final part of this whole is a collection of concurrent containers, which are the .NET 4.0 parallel equivalent of System.Collections.Generic. This article does not cover those latter two, but does touch on that basic unit of concurrency and parallel iteration. It is by no means a substitute for the MSDN documentation that describes these APIs.

Thread creation and destruction are expensive operations. There is a lot of overhead involved with having too many threads, as they can easily waste memory resources and hurt performance due to the operating system having to schedule and content switch between the runnable threads. A process context switch is a seriously heavy-weight operation. To handle this situation, the CLR contains code that internally manages its own thread pool. When the CLR initializes, the thread pool has no threads in it. Internally, the thread pool maintains a queue of operation requests. When your application wants to perform an asynchronous operation, you can call some method that appends an entry into the thread pool's queue. The thread pool's code will extract entries from this queue and dispatch the entry to a thread pool thread. Recall that creating a thread has expensive overhead associated with it. So when a thread pool thread has completed its task, the thread is not destroyed; instead it is returned to the thread pool. If the thread pool contains too many threads at a point when your application is not making requests to the thread pool's internally managed thread mechanism, many threads will outright destroy themselves.

Since the inception of the Task Parallel Library, calling the ThreadPool's QueueUserWorkItem method could have limitations because there is no built-in way for you to know when the operation has completed, and there is no way to get a return value back when the operation completes. To address this issue while parallel computing was being standardized, Microsoft introduced the concept of tasks, and you create them via types in the System.Threading.Tasks namespace. So, rather than calling ThreadPool's QueueUserWorkItem method, you can do the same via tasks:

ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5) // Calling QueueUserWorkItem
new Task(ComputeBoundOp, 5).Start(); // the equivalent of the above using Task

The OS implements threads and provides various unmanaged APIs to create and manage those threads. The CLR wraps these unmanaged threads and exposes them in managed code via the System.Threading.Tasks.Task class, which represents an asynchronous operation. However, a Task does not map directly to an unmanaged thread. Rather, the Task provides a degree of abstraction to the underlying unmanaged thread construct. In .NET 4, we avoid creating a new OS thread every time a Task is created. Instead every time a Task is created, the Tasks request a thread from the thread pool. The work of programming the Tasks involves assigning the set of instructions that Task will execute. Assigning the instructions is heavily dependent on delegates. Examine this code:

using System;
using System.Threading.Tasks;
public class Program
{
    public static void Main()
    {
        const int repetitions = 10000;
        Task task = new Task(() =>
        {
            for (int count = 0; count < repetitions; count++)
            {
                Console.Write('A');
            }
        });
        
        task.Start();
        for (int count = 0; count < repetitions; count++)
        {
            Console.Write('B');
        }
        
        // Wait until the Task completes
        task.Wait();
    }
}

OUTPUT:

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAA
and so on . . .

Notice that after creating the Task object, we immediately call the Start() method, which schedules the task to run. When creating a Task, you must pass a constructor. More to the point, you must pass it an Action (a functional delegate). This is an Action<object> delegate that indicates the operation that you want performed. Yet notice that in this explanation, we are notified that the operation has completed, but we do not have the results of that operation. Has the operation completed with the desired results? Was it cancelled and considered to be completed? That will be discussed shortly. Again, notice that the code that is to run on a new thread is defined in the delegate (of type Action, in this case) passed to the Task constructor. The use of this particular delegate indicates that code will not return the results of a completed operation – just that the operation has completed. In the example shown above, the delegate (in the form of a Lambda expression) prints out B to the console repeatedly during each iteration of the loop. The for loop following the Task declaration is virtually identical, except that it displays A. The resultant output from the program is a series of As until the thread context switches, at which time the program displays the next thread switch. If the work executed in the task returns a result, then any request for the result will automatically block until the task completes.

To get the return results from the operation, examine the next code snippet. It demonstrates Task<TResult>, which returns a value by executing a Func<TResult> rather than simply an Action. To illustrate this concept, we will build three DLLs. The second DLL builds by referencing the first DLL. The third DLL is included to provide the other needed object to build the executable. That is, we will write some to calculate PI via the System.Threading.Tasks.Task class to get the TResult. Here is the base DLL code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public class InternalPiDigitCalculator
        {
            public static int mul_mod(long a, long b, int m)
            {
                return (int)((a * b) % m);
            }
        // return the inverse of x mod y
            public static int inv_mod(int x, int y)
            {
                int q = 0;
                int u = x;
                int v = y;
                int a = 0;
                int c = 1;
                int t = 0;

                do
                {
                    q = v / u;

                    t = c;
                    c = a - q * c;
                    a = t;

                    t = u;
                    u = v - q * u;
                    v = t;
                }
                while (u != 0);

                a = a % y;
                if (a < 0) a = y + a;

                return a;
            }

            // return (a^b) mod m
            public static int pow_mod(int a, int b, int m)
            {
                int r = 1;
                int aa = a;

                while (true)
                {
                    if ((b & 0x01) != 0) r = mul_mod(r, aa, m);
                    b = b >> 1;
                    if (b == 0) break;
                    aa = mul_mod(aa, aa, m);
                }

                return r;
            }

            // return true if n is prime
            public static bool is_prime(int n)
            {
                if ((n % 2) == 0) return false;

                int r = (int)(Math.Sqrt(n));
                for (int i = 3; i <= r; i += 2)
                {
                    if ((n % i) == 0) return false;
                }

                return true;
            }

            // return the prime number immediately after n
            public static int next_prime(int n)
            {
                do
                {
                    n++;
                }
                while (!is_prime(n));

                return n;
            }

            public static int StartingAt(int n)
            {
                int av = 0;
                int vmax = 0;
                int N = (int)((n + 20) * Math.Log(10) / Math.Log(2));
                int num = 0;
                int den = 0;
                int kq = 0;
                int kq2 = 0;
                int t = 0;
                int v = 0;
                int s = 0;
                double sum = 0.0;

                for (int a = 3; a <= (2 * N); a = next_prime(a))
                {
                    vmax = (int)(Math.Log(2 * N) / Math.Log(a));
                    av = 1;

                    for (int i = 0; i < vmax; ++i) av = av * a;

                    s = 0;
                    num = 1;
                    den = 1;
                    v = 0;
                    kq = 1;
                    kq2 = 1;

                    for (int k = 1; k <= N; ++k)
                    {
                        t = k;
                        if (kq >= a)
                        {
                            do
                            {
                                t = t / a;
                                --v;
                            }
                            while ((t % a) == 0);

                            kq = 0;
                        }

                        ++kq;
                        num = mul_mod(num, t, av);

                        t = (2 * k - 1);
                        if (kq2 >= a)
                        {
                            if (kq2 == a)
                            {
                                do
                                {
                                    t = t / a;
                                    ++v;
                                }
                                while ((t % a) == 0);
                            }

                            kq2 -= a;
                        }

                        den = mul_mod(den, t, av);
                        kq2 += 2;

                        if (v > 0)
                        {
                            t = inv_mod(den, av);
                            t = mul_mod(t, num, av);
                            t = mul_mod(t, k, av);
                            for (int i = v; i < vmax; ++i)
                            {
                                t = mul_mod(t, a, av);
                            }
                            s += t;
                            if (s >= av) s -= av;
                        }
                    }

                    t = pow_mod(10, n - 1, av);
                    s = mul_mod(s, t, av);
                    sum = (sum + (double)s / (double)av) % 1.0;
                }

                return (int)(sum * 1e9);
            }
        }    

This file, InternalPiDigitCalculator.cs, is a class library that compiles via the /t:library switch on the .NET 4 command line or as class library project in Visual Studio. This file defines the internals of our next file, PiCalculator.cs, which also compiles into a referenced DLL. Keep in mind that the focus is on waiting for an operation to complete to get the results of that operation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class PiCalculator
    {
        const int Digits = 100;
        #region Helper
        public static string Pi()
        {
            return Calculate();
        }

        public static string Calculate(int digits = 100)
        {
            return Calculate(digits, 0);
        }

        public static string Calculate(int digits, int startingAt)
        {
            System.ComponentModel.DoWorkEventArgs eventArgs = 
            new System.ComponentModel.DoWorkEventArgs(digits);

            CalculatePi(typeof(PiCalculator), eventArgs, startingAt);
            return (string)eventArgs.Result;
        }

        private static void CalculatePi(
            object sender, System.ComponentModel.DoWorkEventArgs eventArgs)
        {
            CalculatePi(sender, eventArgs, 0);
        }

        private static void CalculatePi(
            object sender, 
	System.ComponentModel.DoWorkEventArgs eventArgs, int startingAt)
        {
            int digits = (int)eventArgs.Argument;

            StringBuilder pi;
            if (startingAt == 0)
            {
                pi = new StringBuilder("3.", digits + 2);
            }
            else
            {
                pi = new StringBuilder();
            }
#if BackgroundWorkerThread
            calculationWorker.ReportProgress(0, pi.ToString());
#endif

            // Calculate rest of pi, if required
            if (digits > 0)
            {
                for (int i = 0; i < digits; i += 9)
                {

                    // Calculate next i decimal places
                    int nextDigit = InternalPiDigitCalculator.StartingAt(
                        startingAt + i + 1);
                    int digitCount = Math.Min(digits - i, 9);
                    string ds = string.Format("{0:D9}", nextDigit);
                    pi.Append(ds.Substring(0, digitCount));

                    // Show current progress
#if BackgroundWorkerThread
                    calculationWorker.ReportProgress(
                        0, ds.Substring(0, digitCount));
#endif

#if BackgroundWorkerThread
                    // Check for cancellation
                    if (calculationWorker.CancellationPending)
                    {
                        // Need to set Cancel if you need to  
                        // distinguish how a worker thread completed
                        // ie by checking 
                        // RunWorkerCompletedEventArgs.Cancelled
                        eventArgs.Cancel = true;
                        break;
                    }
#endif
                }
            }

            eventArgs.Result = pi.ToString();
        }
        #endregion
    }
csc.exe /r:InternalPiDigitCalculator /t:library PiCalculator.cs

Now we build the last DLL. This will result in two DLLs to reference:

using System;
using System.Collections.Generic;
using System.Diagnostics; 
public class Utility 
 {
   public static IEnumerable<char /> BusySymbols()
    {
      string busySymbols = @"-\|/-\|/";
      int next = 0;
      while (true)
         {
           yield return busySymbols[next];
           next = (++next) % busySymbols.Length;
           yield return '\b';
         }
      }
    }

Now, keeping the above in mind, consider this notion. Suppose you have to create a bunch of Task objects that share the same state. What about the constructors? To keep you from having to pass the same parameters to each Task is a constructor over and over again, you can create a task factory that encapsulates the common state. The System.Threading.Tasks namespace defines a TaskFactory<TResult> type. Both of these types are derived from System.Object. If you want to create a bunch of tasks that have no return values, then you will construct a TaskFactory. If you want to create a bunch of tasks that have a specific return value, then you will construct a TaskFactory<TResult> where you pass the task’s desired return type for the generic TResult argument.

    using System;
    using System.Threading.Tasks;
    public class Program
    {
        public static void Main()
        {
            Task<string /> task = Task.Factory.StartNew<string />(
                () => PiCalculator.Calculate(100));

            foreach (char busySymbol in Utility.BusySymbols())
            {
                if (task.IsCompleted)
                {
                    Console.Write('\b');
                    break;
                }
                Console.Write(busySymbol);
            }

            Console.WriteLine();
            // Blocks until task completes.
            Console.WriteLine(task.Result);
            System.Diagnostics.Trace.Assert(task.IsCompleted);
        }
    }

This code compiles by referencing the PiCalculator.dll (which is built by referencing the InternalPiDigitCalculator.dll) and the Utility.dll. Here is the output:

sc.exe /r:PiCalculator.dll /r:Utility.dll TaskFact.cs 
3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482
53421170679

Notice that there is no call to task.Start(). Instead it uses the StartNew() method of the static Factory property on Task. The result is similar to instantiating the Task except that the return from Task.Factory.StartNew() is already started. In addition to the IsCompleted property on Task, there are several others worth noting:

  • Status: Status returns a System.Threading.Tasks.TaskStatus enum indicating the status of the task. Values include Created, WaitingForActivation, WaitingToRun, Running, WaitingForChildrenToComplete, RanToCompletion, Canceled, and Faulted.
  • IsCompleted is set to true when a task completes whether it faulted or not. IsCompleted is true whenever the status is RanToCompletion, Canceled, or Faulted.
  • Id: Id is a unique identifier of the task.

AsyncState can track additional data. For example, consider a List of values that various tasks are calculating. One way to place the result into the correct location of the list is to store the list index targeted to contain the result into the AsyncState property. This way, when a task completes, the code can index into the list using the AsyncState (first casting it into an int). A Task includes a ContinueWith() method for chaining tasks together such that as soon as the first one in the chain completes, it triggers the ones that have registered to begin executing after it. Since the ContinueWith() methods return another Task, the chain of work can continue to be added to. The PLINQ and TPL based APIs support only a cancellation request approach in which the target Task opts in to the cancellation request - a process known as cooperative cancelation. Instead of one thread aborting another, the cancellation API requests a Task to cancel. By checking the cancellation flag - a System.Threading.CancellationToken - the Task targeted for cancellation can respond appropriately to the cancellation request.

Canceling a Task using a CancelationToken

You can pass to Task’s constructor a CancellationToken, which allows the Task to be cancelled before it has been scheduled. Examine the code below. After starting the Task, a Console.Read() blocks the main thread. At the same time, the task continues to execute, calculating the next digit of pi and printing it out. Once the user presses ENTER, the execution encounters a call to CancellationTokenSource.Cancel.

   using System;
    using System.Threading;
    using System.Threading.Tasks;

    public class Program
    {
        public static void Main()
        {
            string stars = "*".PadRight(Console.WindowWidth - 1, '*');
            Console.WriteLine("Push ENTER to exit.");

            CancellationTokenSource cancellationTokenSource =
                new CancellationTokenSource();
            Task task = Task.Factory.StartNew(
                () => WritePi(cancellationTokenSource.Token),
                cancellationTokenSource.Token);

            // Wait for the user's input
            Console.ReadLine();

            cancellationTokenSource.Cancel();
            Console.WriteLine(stars);
            task.Wait();
            Console.WriteLine();
        }

        private static void WritePi(
            CancellationToken cancellationToken)
        {
            const int batchSize = 1;
            string piSection = string.Empty;
            int i = 0;

            while (!cancellationToken.IsCancellationRequested
                || i == int.MaxValue)
            {
                piSection = PiCalculator.Calculate(
                    batchSize, (i++) * batchSize);
                Console.Write(piSection);
            }
        }
    }
csc.exe /r:PiCalculator.dll CancelTokenExample.cs
Push ENTER to exit.
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348
253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055
596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486
104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903
600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185
480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247
371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713
427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219
608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594
553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473
035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959
092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138
912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192
550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553
797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935
112533824300355876402474964732639141992726042699227967823547816

*******************************************************************************
3

PI is called a transcendental number: it can be calculated to millions of places without any pattern in the repeating decimal. The above calculation would keep going, but pressing the enter key cancels the operation. While the example code written above seems involved, the point is to remember that, essentially, a task is a lightweight object for managing a parallelizable unit of work. A task avoids the overhead of starting a dedicated thread by using the CLR’s thread pool: this is the same thread pool used by ThreadPool.QueueUserWorkItem, tweaked in CLR 4.0 to work more efficiently with Tasks. Tasks can be used whenever you want to execute something in parallel, being tuned to leverage the multicores.

Executing Iterations in Parallel

Many probably wonder how a Ray Tracer works and how does parallel computing figure in to those famous Ray Tracer examples found at the MSDN Parallel Computing center. In computer animation, rendering is the step where information from the animation files, such as lighting, textures, and shading, is applied to 3D models to generate the 2D image that makes up a frame of the film. Parallel computing is essential to generate the needed number of frames (24 per second) for a feature-length film. The amount of time required to generate a frame has remained relatively constant—as computing power (both the number of processors and the speed of each processor) has increased, it has been exploited to improve the quality of the animation. Cores, not extra speed, provide parallelism and concurrency. So therefore, examining a problem meant to be solved by an algorithm obviously involves finding where concurrency can be achieved in the design process. Once that space has been ascertained, then the next step would be to decide how to structure the data in the design of the algorithm. State loosely, the section above and the section to come might draw the distinction between data parallelism and task parallelism.

It is naive to minimize the value of task parallelism. But conceptually we are breaking into parts a (sort of) function into smaller bits pieces of work to achieve multi-core load balance. The number of methods in a program is rarely dynamic: load balance might result in theory but could leave the program vulnerable to data-sharing problems (data dependence between sub-problems). Data parallelism takes a slightly different approach. The C# for loop loops over an iteration range, and the foreach loop loops over the contents of a collection of data. Consider this basic LINQ code below. But for the sake of performing a read operation that walks over the contents of another collection, just briefly consider this code. Later on, it will make sense when we go into PLINQ:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

public class Program
    {
       public static void Main()
        {
            IEnumerable<process /> processes = Process.GetProcesses().Where(
            process => { return process.WorkingSet64 >  (2 ^ 30); });
            foreach (Process p in processes)
             {
              Console.WriteLine("Process: {0}", p);
              }
          }
     }

The result is a list of the running processes:

Process: System.Diagnostics.Process (spoolsv)
Process: System.Diagnostics.Process (WLIDSVC)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (taskeng)
Process: System.Diagnostics.Process (sqlwriter)
Process: System.Diagnostics.Process (WINZIP32)
Process: System.Diagnostics.Process (audiodg)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (SMSvcHost)
Process: System.Diagnostics.Process (csrss)
Process: System.Diagnostics.Process (Acrobat)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (proc)
Process: System.Diagnostics.Process (dwm)
Process: System.Diagnostics.Process (WINWORD)
Process: System.Diagnostics.Process (wordpad)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (winlogon)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (SeaPort)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (lsm)
Process: System.Diagnostics.Process (SLsvc)
Process: System.Diagnostics.Process (sqlservr)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (lsass)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (smss)
Process: System.Diagnostics.Process (FNPLicensingService)
Process: System.Diagnostics.Process (services)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (ntvdm)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (DkService)
Process: System.Diagnostics.Process (GoogleToolbarNotifier)
Process: System.Diagnostics.Process (Mp4Player)
Process: System.Diagnostics.Process (WZQKPICK)
Process: System.Diagnostics.Process (CISVC)
Process: System.Diagnostics.Process (taskeng)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (wordpad)
Process: System.Diagnostics.Process (wininit)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (explorer)
Process: System.Diagnostics.Process (cmd)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (csrss)
Process: System.Diagnostics.Process (notepad)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (SearchIndexer)
Process: System.Diagnostics.Process (mswinext)
Process: System.Diagnostics.Process (WLIDSVCM)
Process: System.Diagnostics.Process (inetinfo)
Process: System.Diagnostics.Process (System)
Process: System.Diagnostics.Process (svchost)
Process: System.Diagnostics.Process (Idle)

In that example, we just merely read the contents of a list of processes. But consider LINQ-to-Objects. A PLINQ query provider takes any LINQ-to-Objects query over in-memory data structures and auto-parallelizes it indirectly by using TPL. PLINQ will be discussed shortly.

Parallel.For and Parallel.ForEach: Derived from the Parallel Class

In programming, the for loop is the only control-flow structure that steps over every element in an array of data. Yet while the for loop executes each iteration synchronously and in independent pieces, it is not necessary to complete the pieces sequentially. as long as they are still all appended sequentially. The sub-pieces then have to be appended to form a single result. Therefore, imagine if you could have iterations run simultaneously, overlapping each other because each processor could take an iteration and execute it in parallel with other processors executing other iterations. Given the simultaneous execution of iterations, we could decrease the execution time more and more based on the number of processors. So consider some basic code that use the Parallel.For method. This code references our PiCalcualtor.dll:

using System;
using System.Threading.Tasks;
public class App
 {
    const int TotalDigits = 100;
    const int BatchSize = 10;

        public static void Main()
        {
            string pi = null;
            int iterations = TotalDigits / BatchSize;
            string[] sections = new string[iterations];
            Parallel.For(0, iterations, (i) =>
            {
                sections[i] += PiCalculator.Calculate(
                    BatchSize, i * BatchSize);
            });
            pi = string.Join("", sections);
            Console.WriteLine(pi);
        }
    }
csc.exe /r:PiCalculator.dll PForExample.cs
3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034
   8253421170679

As with the for loop, the call to the Parallel.For API will not complete until all iterations are complete. That is, by the time execution reaches the string.Join() statement, all sections of pi will have been executed. But notice that the code for combining certain sections of pi does not occur inside the iteration. Suffice it for now to know that the execution at these points is neither thread-safe nor atomic. To handle this problem, each section of pi is stored in an array and two or more iterations will access a single element within the array simultaneously. Anyone who has spent time writing managed threads knows that sometimes you need to block the current thread's execution until another target thread has completed. The Join method allows you to do this. Now only when all sections of pi are calculated does string.Join() combine them. Consequently, we postpone concatenating the sections until after the Parallel.For loop has completed.

A ForEach loop works like a For loop. The source collection is partitioned and the work is scheduled on multiple threads based on the system environment. The more processors on the system, the faster the parallel method runs. For some source collections, a sequential loop may be faster, depending on the size of the source, and the kind of work being performed. Now this code has been referenced from the MSDN library:

using System;
using System.Drawing; // requires system.Drawing.dll
using System.IO;
using System.Threading;
using System.Threading.Tasks;

    class SimpleForEach
    {
      static void Main()
        {
            
        string[] files = 
        System.IO.Directory.GetFiles
		(@"C:\Users\Public\Pictures\Sample Pictures", "*.jpg");
            string newDir = @"C:\Users\Public\Pictures\Sample Pictures\Modified";
            System.IO.Directory.CreateDirectory(newDir);

            //  Method signature: Parallel.ForEach
            // (IEnumerable<tsource> source, Action<tsource> body)
            Parallel.ForEach(files, currentFile =>
            {
                // The more computational work you do here, the greater 
                // the speedup compared to a sequential foreach loop.
                string filename = System.IO.Path.GetFileName(currentFile);
                System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(currentFile);

                bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
                bitmap.Save(System.IO.Path.Combine(newDir, filename));

                // Peek behind the scenes to see how work is parallelized.
                // But be aware: Thread contention for 
                // the Console slows down parallel loops!!!
                Console.WriteLine("Processing {0} on thread {1}", filename,
                                    Thread.CurrentThread.ManagedThreadId);

            } //close lambda expression
                 ); //close method invocation

            // Keep the console window open in debug mode.
            Console.WriteLine("Processing complete. Press any key to exit.");
            Console.ReadKey();
        }
    }
}

This code compiles with an external reference to System.Drawing.dll. If you run this code, check your Sample Pictures folder and you will see another folder (subdirectory) created containing the redrawn samples. On a different note, the Parallel.Invoke method executes an array of Action delegates in parallel and then waits for them to complete. If you examine this code, you will see another namespace, System.Concurrent.Collections. This namespace is provided by Parallel Extensions as a concurrent container. Notice the use of BigInt as a data type. This basic example involves a large (and recursive) factorial computation. It is meant to illustrate the creation of a factorial delegate, the creation of an array of actions that follow, the computations (performed in parallel), and the printing of the results. Take care to note that this code compiles on the command line with a reference to System.Numerics.dll: /r:System.Numerics.dll.

using System;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
class EntryPoint
{
const int FactorialsToCompute = 100;
static void Main() {
var numbers = new ConcurrentDictionary<BigInteger, BigInteger>();
// Create a factorial delegate.
Func<BigInteger, BigInteger> factorial = null;
factorial = (n) => ( n == 0 ) ? 1 : n * factorial(n-1);
// Build the array of actions
var actions = new Action[FactorialsToCompute];
for( int i = 0; i < FactorialsToCompute; ++i ) {
int x = i;
actions[i] = () => {
numbers[x] = factorial(x);
};
}
// Now compute the values.
Parallel.Invoke( actions );
// Print out the results.
for( int i = 0; i < FactorialsToCompute; ++i ) {
Console.WriteLine( numbers[i] );
   }
  }
}

OUTPUTS

1
1
2
6
24
120
720
5040
40320
362880
3628800
39916800
479001600
6227020800
87178291200
1307674368000
20922789888000
355687428096000
6402373705728000
121645100408832000
2432902008176640000
51090942171709440000
1124000727777607680000
25852016738884976640000
620448401733239439360000
15511210043330985984000000
403291461126605635584000000
10888869450418352160768000000
304888344611713860501504000000
8841761993739701954543616000000
265252859812191058636308480000000
8222838654177922817725562880000000
263130836933693530167218012160000000
8683317618811886495518194401280000000
295232799039604140847618609643520000000
10333147966386144929666651337523200000000
371993326789901217467999448150835200000000
13763753091226345046315979581580902400000000
523022617466601111760007224100074291200000000
20397882081197443358640281739902897356800000000
815915283247897734345611269596115894272000000000
33452526613163807108170062053440751665152000000000
1405006117752879898543142606244511569936384000000000
736497978881168458687447040000000000000 . .  and so on for about a hundred more lines

Parallel Language Integrated Query

The static System.Linq.ParallelEnumerable class (defined in System.Core.dll) implements all of the Parallel LINQ functionality, and so you must import the System.Linq namespace into your source code via the C# using directive. This class exposes parallel versions of all the standard LINQ operators such as Where, Select, SelectMany, GroupBy, Join, OrderBy, Skip, Take, and so on. All of these methods are extension methods that extend the System.Linq.ParallelQuery type. To have your LINQ to Objects query invoke the parallel versions of these methods, you must convert your sequential query (based on IEnumerable or IEnumerable) to a parallel query (based on ParallelQuery or ParallelQuery) using ParallelEnumerable’s AsParallel extension method. Here is an example. We create a source of data and place it in the form of an array, perform (in parallel) an operation over each element in the sequence to yield a transformed output, and then print the results. The idea is to take the numbers from to ten, square them, output the contents the data, and total of the processing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
public class Program {
public static void Main(string[] args) {

            // create some source data
            int[] sourceData = new int[10];
            for (int i = 0; i < sourceData.Length; i++) {
                sourceData[i] = i;
            }

            Console.WriteLine("Defining PLINQ query");
            // define the query
            IEnumerable<double> results =
                sourceData.AsParallel().Select(item => {
                    Console.WriteLine("Processing item {0}", item);
                    return Math.Pow(item, 2);
                });

            Console.WriteLine("Waiting...");
            Thread.Sleep(5000);

            // sum the results - this will trigger
            // execution of the query
            Console.WriteLine("Accessing results");
            double total = 0;
            foreach (double d in results) {
                total += d;
            }
            Console.WriteLine("Total {0}", total);

            // wait for input before exiting
            Console.WriteLine("Press enter to finish");
            Console.ReadLine();
        }
    }

OUTPUT

Defining PLINQ query
Waiting...
Accessing results
Processing item 0
Processing item 1
Processing item 2
Processing item 5
Processing item 6
Processing item 7
Processing item 3
Processing item 4
Processing item 8
Processing item 9
Total 285
Press enter to finish

So what is the difference between LINQ and PLINQ. In the example above, we used the AsParallel operator that invokes a parallel query. But to get a feel for PLINQ, consider an example that uses a text file as a data source. .NET 4 introduced new overloads into the System.IO.File and System.IO.Directory classes. File.ReadLines previously returned a string[] of all lines in a file. A new overload returns an IEnumerable<string>, which allows you to iterate a file (using this method, you could always iterate the file manually.) without loading the entire contents of the file into memory. The name of the file is called AllCountries.txt. The AU.txt and FeaturedCodes.txt used later be can also be obtained from http://download.geonames.org/export/dump.

We will first perform a (sequential) LINQ query to iterate all of the lines of this very lengthy file (the file contains all the Geonames content) to look for all places with an elevation greater than 8000 meters and then ordering them from the highest elevation to the lowest elevation. We use a Stopwatch timer in this code to see how long this sequential query takes and how much CPU it consumes. Afterwards, we will use PLINQ on the same file. Extract the file, and then copy and paste it into your C: directory. It is one of three text files that we will use. Then run this code, and ideally, take note of how it works:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Diagnostics;
public class Program {
public static void Main() {

  const int nameColumn = 1;
  const int countryColumn = 8;
  const int elevationColumn = 15;
 
  Stopwatch watch = new Stopwatch();
  watch.Start();    
 
    var lines = File.ReadLines(Path.Combine(
        Environment.CurrentDirectory, @"C:\AllCountries.txt"));
 
    var q = from line in lines
            let fields = line.Split(new char[] { '\t' })
            let elevation = string.IsNullOrEmpty(
                     fields[elevationColumn]) ?
                     0 : int.Parse(fields[elevationColumn])
            where elevation > 8000 // elevation in m's
            orderby elevation descending
            select new
            {
                name = fields[nameColumn] ?? "",
                elevation = elevation,
                country = fields[countryColumn]
            };
 
    foreach (var x in q)
    {
        if (x != null)
            Console.WriteLine("{0} ({1}m) – located in {2}",
                x.name, x.elevation, x.country);
    }
 
    Console.WriteLine("Elapsed time: {0}ms",
        watch.ElapsedMilliseconds);
   }
} 

Be patient when you run this code. The results take some time, as they are shown below:

Mount Everest (8848m) - located in NP
K2 (8611m) - located in PK
Señal Salumpite (8600m) - located in PE
Kanchenjunga (8586m) - located in NP
Lo-tzu Feng (8516m) - located in NP
Makalu (8463m) - located in NP
Qowowuyag (8188m) - located in CN
Dhaulagiri (8167m) - located in NP
Manaslu (8163m) - located in NP
Nanga Parbat (8125m) - located in PK
Annapurna Himal (8091m) - located in NP
Annapurna I (8091m) - located in NP
Gasherbrum Shan (8080m) - located in PK
Broad Feng (8051m) - located in PK
Gasherbrum II Feng (8034m) - located in PK
Xixabangma Feng (8027m) - located in CN
Broad Peak Central (8011m) - located in PK
Elapsed time: 31677ms

Now we will query the file using PLINQ:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Diagnostics;
public class Program {
public static void Main() {

 const int nameColumn = 1;
    const int countryColumn = 8;
    const int elevationColumn = 15;
 
    Stopwatch watch = new Stopwatch();
    watch.Start();
 
    var lines = File.ReadLines(Path.Combine(
        Environment.CurrentDirectory, @"C:\AllCountries.txt"));
 
    var q = from line in lines.AsParallel()
            let fields = line.Split(new char[] { '\t' })
            let elevation = string.IsNullOrEmpty(
                     fields[elevationColumn]) ?
                     0 : int.Parse(fields[elevationColumn])
            where elevation > 8000 // elevation in m's
            orderby elevation descending
            select new
            {
                name = fields[nameColumn] ?? "",
                elevation = elevation,
                country = fields[countryColumn]
            };
 
    foreach (var x in q)
    {
        if (x != null)
            Console.WriteLine("{0} ({1}m) - located in {2}",
                x.name, x.elevation, x.country);
    }
 
    Console.WriteLine("Elapsed time: {0}ms",
        watch.ElapsedMilliseconds);
    }
 }

OUTPUT

Mount Everest (8848m) - located in NP
K2 (8611m) - located in PK
Señal Salumpite (8600m) - located in PE
Kanchenjunga (8586m) - located in NP
Lo-tzu Feng (8516m) - located in NP
Makalu (8463m) - located in NP
Qowowuyag (8188m) - located in CN
Dhaulagiri (8167m) - located in NP
Manaslu (8163m) - located in NP
Nanga Parbat (8125m) - located in PK
Annapurna I (8091m) - located in NP
Annapurna Himal (8091m) - located in NP
Gasherbrum Shan (8080m) - located in PK
Broad Feng (8051m) - located in PK
Gasherbrum II Feng (8034m) - located in PK
Xixabangma Feng (8027m) - located in CN
Broad Peak Central (8011m) - located in PK
Elapsed time: 16099ms

Although the query didn't run twice as fast (as might be expected on a dual-core machine), the speed improvement is significant and was solely achieved by adding a .AsParallel() to the sequential query. Having said that, recall that AllCountries.txt file. The other test files are called AU.txt and FeaturedCodes.txt. If those three files are, say, extracted to your desktop to be copied and pasted to your C: directory, this code will run. Now we are going to query the main text file and query the associated text files to grab the corresponding information that we base our query on. Consider this code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Diagnostics;
public class Program {
public static void Main() {

  const int nameColumn = 1;
    const int featureClassColumn = 6;
    const int featureCodeColumn = 7;
    const int countryColumn = 8;
    const int elevationIndexColumn = 15;
 
    Stopwatch watch = new Stopwatch();
    watch.Start();
 
    // load in the feature class and code decoding data into an array.
    var codeFile = File.ReadLines(
        Path.Combine(Environment.CurrentDirectory, @"C:\FeatureCodes.txt"));
 
    var codes = (from code in codeFile.AsParallel()
                 let c = code.Split(new char[] { '\t' })
                 select new
                 {
                     featureClass = c[0][0],
                     featureCode = c[0].Remove(0, 2),
                     featureDescription = c[1]
                 })
                 .ToArray();
 
    
    //var lines = File.ReadLines(Path.Combine(Environment.CurrentDirectory, @"C:\AU.txt"));
 
    var lines = File.ReadLines(Path.Combine(Environment.CurrentDirectory,
     @"C:\AllCountries.txt"));
 
    var q = from line in lines.AsParallel()
            let fields = line.Split(new char[] { '\t' })
            let elevation = string.IsNullOrEmpty(fields[elevationIndexColumn]) ?
                                     0 : int.Parse(fields[elevationIndexColumn])
            where elevation > 6000 // elevation in m's
            let code = codes.SingleOrDefault(
                         c => c.featureCode == fields[featureCodeColumn] &&
                              c.featureClass == fields[featureClassColumn][0])
            orderby elevation descending
            select new
            {
                name = fields[nameColumn] ?? "",
                elevation = elevation,
                country = fields[countryColumn],
                description = code != null ? code.featureDescription : ""
            };
 
    foreach (var x in q)
    {
        if (x != null)
            Console.WriteLine("{0} ({1}m) – A {2} in {3}",
                x.name,
                x.elevation,
                x.description,
                x.country);
    }
 
    Console.WriteLine();
    Console.WriteLine("Elapsed time: {0}ms", watch.ElapsedMilliseconds);
   }
 }

Lastly, here are the results of the query:

Mount Everest (8848m) - A mountain in NP
K2 (8611m) - A mountain in PK
Se¤al Salumpite (8600m) - A triangulation station in PE
Kanchenjunga (8586m) - A mountain in NP
Lo-tzu Feng (8516m) - A peak in NP
Makalu (8463m) - A mountain in NP
Qowowuyag (8188m) - A mountain in CN
Dhaulagiri (8167m) - A peak in NP
Manaslu (8163m) - A mountain in NP
Nanga Parbat (8125m) - A peak in PK
Annapurna I (8091m) - A mountain in NP
Annapurna Himal (8091m) - A area in NP
Gasherbrum Shan (8080m) - A mountain in PK
Broad Feng (8051m) - A mountain in PK
Gasherbrum II Feng (8034m) - A mountain in PK
Xixabangma Feng (8027m) - A mountain in CN
Broad Peak Central (8011m) - A mountain in PK
Gasherbrum III Feng (7952m) - A mountain in PK
Gyachung Kang (7952m) - A mountain in CN
Annapurna II (7937m) - A mountain in NP
Gasherbrum IV Feng (7925m) - A mountain in PK
Ngozumpa Kang I (7916m) - A mountain in NP
Himal Chuli (7893m) - A peak in NP
Disteghil Sar (7885m) - A mountain in PK
Ngadi Chuli (7871m) - A mountain in NP
Nuptse (7864m) - A mountain in NP
Khinyang Chhish (7852m) - A peak in PK
Masherbrum East (7821m) - A mountain in PK
Chomo Lonzo (7818m) - A mountain in CN
Nanda Devi (7816m) - A mountain in IN
North Peak (7809m) - A peak in PK
Masherbrum West (7805m) - A mountain in PK
Batura Sar (7795m) - A mountain in PK
Rakaposhi Group (7788m) - A mountains in PK
Namjagbarwa Feng (7782m) - A mountain in CN
Kanjut Sar (7760m) - A mountain in PK
Gasherbrum II East (7758m) - A peak in CN
Kamet (7756m) - A mountain in IN
Dhaulagiri II (7751m) - A mountain in NP
Ngozumpa Kang II (7743m) - A mountain in NP
Saltoro Kangri I (7742m) - A peak in PK
Kumbhakarna (7711m) - A peak in NP
Tirich Mir (7708m) - A peak in PK
Phola Gangchhen (7703m) - A mountain in CN
Gurla Mandhata (7694m) - A mountain in CN
Makalu II (7678m) - A peak in NP
Saser Kangri (7672m) - A mountain in IN
Chogolisa I (7668m) - A peak in PK
Dhaulagiri IV (7661m) - A mountain in NP
Kongur Shan (7649m) - A mountain in CN
Fang (7647m) - A peak in NP
Dhaulagiri V (7618m) - A peak in NP
Shispare (7611m) - A mountain in PK
Trivor (7577m) - A mountain in PK
Gangkhar Puensum (7570m) - A mountain in BT
Chomo Lonzo Central (7565m) - A mountain in CN
Gongga Shan (7556m) - A mountain in CN
Annapurna III (7555m) - A peak in NP
Kula Kangri (7554m) - A mountain in CN
Skyang Kangri (7545m) - A mountain in CN
Skyang Kangri (7543m) - A peak in PK
Liangkang Kangri (7534m) - A mountain in BT
Annapurna IV (7525m) - A peak in NP
Pik Imeni Ismail Samani (7495m) - A peak in TJ
Pumarikish (7492m) - A mountain in PK
Noshak (7482m) - A mountain in AF
Jongsong Peak (7462m) - A peak in IN
Malubiting (7458m) - A peak in PK
Gangapurna (7455m) - A peak in NP
Kangsar Kang (7454m) - A peak in NP
Sia Kangri (7421m) - A peak in PK
Haramosh (7397m) - A peak in PK
Istoro Nal (7397m) - A mountain in PK
Labuche Kang (7367m) - A mountain in CN
Tent Peak (7362m) - A peak in IN
Gimmigela Chuli I (7350m) - A mountain in NP
Momhil Sar (7342m) - A peak in PK
Bojohaghur Duanasir (7328m) - A peak in PK
Chamlang (7319m) - A mountain in NP
Cho-mo-la-li Shan (7314m) - A mountain in BT
Baltoro Kangri (7312m) - A peak in PK
Sherpi Kangri (7303m) - A peak in PK
Gang Benchhen (7299m) - A mountain in CN
Porong Ri (7292m) - A mountain in CN
Baintha Brakk (7284m) - A peak in PK
Muztagh Tower (7278m) - A peak in PK
Labuche Kang II (7250m) - A mountain in CN
Kangphu Kang I (7220m) - A mountain in BT
Annapurna South (7219m) - A peak in NP
Tongshanjiabu (7207m) - A mountain in BT
Tarke Kang (7193m) - A peak in NP
Nepal Peak (7177m) - A peak in NP
Nyainqˆntanglha Feng (7162m) - A mountain in CN
Nunkun (7135m) - A peak in IN
Gauri Sankar (7134m) - A mountain in NP
Tilicho Peak (7134m) - A peak in NP
P'ao-han-li Shan (7128m) - A mountain in IN
Chomolhari Kang (7121m) - A mountain in BT
Rakhiot (7074m) - A peak in PK
Nilgiri North (7061m) - A peak in NP
Ghenishchish (7027m) - A peak in PK
Machhapuchhare (6993m) - A mountain in NP
Lamjung Himal (6983m) - A mountain in NP
Muztag Feng (6973m) - A mountain in CN
Cerro Aconcagua (6959m) - A mountain in AR
Nilgiri (6940m) - A mountain in NP
Kuk Sar (6934m) - A peak in PK
Tukuche Peak (6920m) - A mountain in NP
Cerro Ojos del Salado (6891m) - A mountain in CL
Khangchengyao (6889m) - A mountain in IN
Monte Pissis (6882m) - A mountain in AR
Lunkho (6872m) - A mountain in PK
Angelus (6855m) - A peak in PK
Xiaofong Tip (6845m) - A peak in CN
Nilgiri South (6839m) - A peak in NP
Chongra (6824m) - A peak in PK
Ama Dablan (6812m) - A mountain in NP
Cerro Mercedario (6770m) - A mountain in AR
Cerro Bonete (6759m) - A mountain in AR
Cerro Pariamachay (6759m) - A mountain in PE
Nevado Tres Cruces (6749m) - A mountain in AR
Nevado Huascar n (6746m) - A mountain in PE
Kagebo Feng (6740m) - A peak in CN
Cerro Llullaillaco (6730m) - A volcano in CL
Kangrinboqˆ Feng (6714m) - A mountain in CN
Koz Sar (6677m) - A peak in PK
Ghul Lasht Zom (6654m) - A mountain in PK
Tahu Rutum (6650m) - A peak in PK
Cerro de Incahuasi (6621m) - A mountain in AR
Nevado Yerupaj  (6617m) - A mountain in PE
Makrong Chhish (6602m) - A peak in PK
Paiju (6598m) - A peak in PK
Lukpe Lawo Brakk (6592m) - A peak in PK
Chulu (6584m) - A mountain in NP
Cerro Tupungato (6570m) - A mountain in CL
Buni Zom (6551m) - A mountain in PK
Nevado El Muerto (6488m) - A mountain in AR
yak Gawa (6484m) - A mountain in NP
Thorungste (6482m) - A mountain in NP
Sinu Chuli (6461m) - A peak in NP
West Chongra (6446m) - A peak in PK
Hiunchuli (6441m) - A peak in NP
Cerro Bayo (6436m) - A mountain in AR
Koh-e Toluksay (6435m) - A mountain in AF
Bobisghir (6416m) - A peak in PK
Pir Yakhha-ye Darah Sakhi (6414m) - A valley in AF
Sosbun Brakk (6413m) - A peak in PK
Volc n Antofalla (6409m) - A cone(s) in AR
Koser Gunge (6400m) - A peak in PK
Cerro El Libertador (6380m) - A mountain in AR
Nevado Coropuna (6377m) - A mountain in PE
Cerro El C¢ndor (6373m) - A mountain in AR
Tozˆ Kangri (6370m) - A mountain in CN
Taweche (6367m) - A mountain in NP
Nevado Siula Grande (6344m) - A mountain in PE
Nera (6339m) - A peak in PK
Spe Syngo Sar (6335m) - A peak in PK
Kalejandar (6325m) - A mountains in PK
Kupuitung Kung (6321m) - A mountain in PK
Shah Dok (6320m) - A mountain in PK
Bullah (6294m) - A mountain in PK
Achar Zom (6291m) - A mountain in PK
Nevado Ampato (6288m) - A mountain in PE
Nevado Pomerape (6282m) - A mountain in BO
Dongbar (6281m) - A mountain in PK
Gama Sokha Lumbu (6281m) - A mountain in PK
Sakar Sar (6271m) - A mountain in AF
Kuh-e Bandaka (6271m) - A mountain in AF
Chimborazo (6268m) - A volcano in EC
Ghochhar Sar (6262m) - A peak in PK
Marble Peak (6256m) - A peak in PK
Cristal (6252m) - A peak in PK
Khurdopin Sar (6251m) - A peak in PK
Garmush (6244m) - A mountain in PK
Dehli Sang-i-Sar (6225m) - A mountain in PK
Qal`ah Sorkhi (6171m) - A mountain in AF
Bilchhar Dobani (6134m) - A mountain in PK
Shilla (6132m) - A mountain in IN
Koh-e Munjan (6130m) - A mountain in AF
Canharba Chuli (6128m) - A peak in NP
Nevado Sarapo (6127m) - A mountain in PE
Nevado Hualc n (6122m) - A mountain in PE
Windy Gap (6111m) - A pass in PK
Tupopdan (6106m) - A peak in PK
South Peak (6105m) - A mountain in US
Churchill Peaks (6105m) - A mountain in US
Mount McKinley (6105m) - A mountain in US
Nevado Jirishjanca (6094m) - A mountain in PE
Nevado Solimana (6093m) - A mountain in PE
Pisang Peak (6091m) - A mountain in NP
Ragh Shur (6089m) - A mountain in PK
Ishpindar Sor (6089m) - A mountain in PK
Nevado Chachani (6057m) - A mountain in PE
Jurjur Khona Sar (6054m) - A peak in PK
Volc n Socompa (6050m) - A volcano in CL
Farthing Horn (6034m) - A cape in US
Carter Horn (6031m) - A cape in US
Nevado Hualca Hualca (6025m) - A mountain in PE
Nevado Rasac (6017m) - A mountain in PE
Mitre (6013m) - A peak in PK
Dhampus Peak (6012m) - A mountain in NP
Koh-e Sar-e Tund (6001m) - A mountain in AF

Elapsed time: 16119ms

The output is extremely lengthy, but it is not the intention of the writer to try and fill up a page. These are the results of the query via PLINQ. To conclude, with the referenced material based on Jeffrey Richter’s book “The CLR via C#, the 3rd Edition”, some other parts were referenced from the book “LINQ to Objects using C# 4.0”, written by Troy Magennis. Both books are extremely informative and are suggested reading.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

logicchild
Other Pref. Trust
United States United States
I started electronics training at age 33. I began studying microprocessor technology in an RF communications oriented program. I am 43 years old now. I have studied C code, opcode (mainly x86 and AT+T) for around 3 years in order to learn how to recognize viral code and the use of procedural languages. I am currently learning C# and the other virtual runtime system languages. I guess I started with the egg rather than the chicken. My past work would indicate that my primary strength is in applied mathematics.

Comments and Discussions

 
QuestionHello Pinmemberserefrose6-May-13 5:54 
QuestionCredit where credit is due Pinmemberwaroberts22-Feb-13 6:51 
GeneralI am just about to start a series of articles on this, will more than likely cover some of this same ground PinmvpSacha Barber25-Jan-11 6:55 
GeneralRe: I am just about to start a series of articles on this, will more than likely cover some of this same ground Pinmvplogicchild25-Jan-11 7:07 
GeneralRe: I am just about to start a series of articles on this, will more than likely cover some of this same ground PinmvpSacha Barber25-Jan-11 9:46 
GeneralGreat PinmemberCiumac Sergiu18-Nov-10 21:56 
GeneralMy vote of 5 PinsubeditorWalt Fair, Jr.18-Nov-10 13:17 
GeneralMy vote of 5 PinmemberTomChantler10-Nov-10 7:32 
GeneralNo return type for first task - codeproject code formatter error PinmemberTomChantler10-Nov-10 7:30 
GeneralReally detailed and useful! PinmemberWayne Ye7-Nov-10 20:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 18 Nov 2010
Article Copyright 2010 by logicchild
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid