Thursday, 20 March 2014

Parallel Computing in .NET Framework 4.0 - Part 2

Parallel Computing in .NET Framework 4.0


Computers in the near future are expected to have significantly more cores. To take advantage of the hardware, you can parallelize your code to distribute work across multiple processors. In the past, parallelization required low-level manipulation of threads and locks
Visual Studio 2010 and the .NET Framework 4 enhance support for parallel programming by providing a new runtime, new class library types, and new diagnostic tools. These features simplify parallel development so that you can write efficient, fine-grained, and scalable parallel code in a natural idiom without having to work directly with threads or the thread pool.
On the higher level, .NET framework 4.0 provided two major libraries for parallel programming. These are the Task Parallel Library (TPL) and the parallel version of Language-Integrated Query (PLINQ).
Note: Starting with the .NET Framework 4, the TPL is the preferred way to write multithreaded and parallel code. However, not all code is suitable for parallelization; for example, if a loop performs only a small amount of work on each iteration, or it doesn't run for many iterations, then the overhead of parallelization can cause the code to run more slowly.

To know more about Parallel Computing, please visit:
http://msdn.microsoft.com/en-us/library/dd460693(v=vs.100).aspx

Task Parallel Library [TPL]

The Task Parallel Library (TPL) is a set of public types and APIs in the System.Threading and System.Threading.Tasks namespaces in the .NET Framework version 4. The purpose of the TPL is to make developers more productive by simplifying the process of adding parallelism and concurrency to applications. The TPL scales the degree of concurrency dynamically to most efficiently use all the processors that are available.
In addition, the TPL handles the partitioning of the work, the scheduling of threads on the ThreadPool, cancellation support, state management, and other low-level details. By using TPL, you can maximize the performance of your code while focusing on the work that your program is designed to accomplish.
Starting with the .NET Framework 4, the TPL is the preferred way to write multithreaded and parallel code. The Task Parallel Library provides parallelism based upon both data and task decomposition. Data parallelism is simplified with new versions of for loop and foreach loop that automatically decompose the data and separate the iterations onto all available processor cores.
Task parallelism is provided by new classes that allow tasks to be defined using lambda expressions. You can create tasks and let the .NET framework determine when they will execute and which of the available processors will perform the work.

Data Parallelism (Task Parallel Library)

Data parallelism is usually applied to large data processing tasks. Data parallelism applicable to operations which performed concurrently (that is, in parallel) on elements in a source collection. Data parallelism syntax is supported by several overloads of the for and foreach methods in the System.Threading.Tasks.Parallel class.
In data parallel operations, the source collection is partitioned so that multiple threads can operate on different segments concurrently. System.Threading.Tasks.Parallel class provides method-based parallel implementations of for and foreach loops (For and For Each in Visual Basic).
You write the loop logic for a Parallel.For or Parallel.ForEach loop much as you would write a sequential loop. You do not have to create threads or queue work items. In basic loops, you do not have to take locks. The TPL handles all the low-level work for you.
Note: Many examples use lambda expressions to define delegates in TPL. If you are not familiar with lambda expressions in C# or Visual Basic, visit http://msdn.microsoft.com/en-us/library/dd460699(v=vs.100).aspx for Lambda Expressions in PLINQ / TPL.
Lambda Expressions (C#): A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types. All lambda expressions use the lambda operator =>, which is read as "goes to". The left side of the lambda operator specifies the input parameters (if any) and the right side holds the expression or statement block. The lambda expression x => x * x is read "x goes to x times x."
The => operator has the same precedence as assignment (=) and is right-associative. Lambdas are used in method-based LINQ queries as arguments to standard query operator methods such as Where.
To learn the Lambda Expressions in C# follow the below link:
1. Parallel.For: Here we will consider the parallel for loop. This provides some of the functionality of the basic for loop, allowing you to create a loop with a fixed number of iterations. If multiple cores are available, the iterations can be decomposed into groups that are executed in parallel.
Potential Pitfalls in Parallel.For loop: After working the above examples we can say it is very easy to implement and work with Parallel.For, but as developer we need to look some potential and usually unanticipated disaster or difficulty related to this. There are various issues that can be encountered. Some cause immediately noticeable bugs in your code. Some cause subtle bugs that may occur only rarely and are difficult to find. Others simply lower the performance of parallel loops.
·  Shared State: Parallel loops are ideal when the individual iterations are independent. When the iterations share mutable state, synchronization is necessary to ensure that errors are not introduced by parallel processes using inconsistent values. This usually requires the introduction of locking mechanisms that slow the performance of the software or changes to algorithms to remove shared state.
·  Dependent Iterations: With sequential loops you can assume that all earlier iterations will be completed before the current execution. With parallel loops, as seen in the first example, the order is usually changed. This means that you should not have code within a parallel loop that depends upon another iteration's result.
·  Excessive Parallelism: In general case parallelism increases the performance of loops. However, in many case it is possible to overuse parallelism which may decrease the performance.
·  Calls to Thread-Safe Methods: If the methods that you call from within a loop are thread-safe, you should not generate synchronization problems through their use. However, if the methods use locking to achieve thread-safety, you may spoil the performance of your software as multiple cores become blocked when your parallel loop executes.
·  Myth of Parallelism: A common myth is that a loop will always execute in parallel will increase the performance. On single core processors parallel loops will generally execute sequentially. Even on multiprocessor systems it is possible for a loop to run in series. If your code requires that a later iteration completes before an earlier one can continue, the loop will be deadlocked.
2. Parallel.ForEach: The parallel ForEach loop provides a parallel version of the standard, sequential foreach loop. Each iteration processes a single item from a collection. However, the parallel nature of the loop means that multiple iterations may be executing at the same time on different processors or processor cores. This opens up the possibility of synchronization problems so the loop is ideally suited to processes where each iteration is independent of the others.
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.

We have many more points to study, I given you the basic overview of Data Parallelism using for and foreach loop. Below are list of some useful points which you can study yourself and create some examples:
Termination of Parallel Loops
  • Parallel Loop State
  • ParallelLoopState.Break
  • LowestBreakIteration
  • ParallelLoopState.Stop
Synchronization in Parallel Loops
  • Aggregation in Sequential Loops
  • Aggregation in Parallel Loops
  • Synchronization Using Locking
  • Local Loop State in For Loops

Task Parallelism (Task Parallel Library)

The Task Parallel Library (TPL), as its name implies, is based on the concept of the task. The term task parallelism refers to one or more independent tasks running concurrently. A task represents an asynchronous operation, and in some ways it resembles the creation of a new thread or ThreadPool work item, but at a higher level of abstraction. Tasks provide two primary benefits:
·    More efficient and more scalable use of system resources: Behind the scenes, tasks are queued to the ThreadPool, which has been enhanced with algorithms (like hill-climbing) that determine and adjust to the number of threads that maximizes throughput. This makes tasks relatively lightweight, and you can create many of them to enable fine-grained parallelism. To complement this, widely-known work-stealing algorithms are employed to provide load-balancing.
·   More programmatic control than is possible with a thread or work item: Tasks and the framework built around them provide a rich set of APIs that support waiting, cancellation, continuations, robust exception handling, detailed status, custom scheduling, and more.
Note: For both of these reasons, in the .NET Framework 4, tasks are the preferred API for writing multi-threaded, asynchronous, and parallel code.
Why we need Task Parallelism? - Some algorithms do not lend themselves to data decomposition because they are not repeating the same action. However, they may be candidates for task decomposition. This is where an algorithm is broken into sections that can be executed independently. Each section is considered to be a separate task that may be executed on its own processor core, with several tasks running concurrently. This type of decomposition is usually more difficult to implement and sometimes requires that an algorithm be changed substantially or replaced entirely to minimize elements that must be executed sequentially and to limit shared mutable values.
1. Creating and Running Tasks Implicitly [Parallel.Invoke]: The Parallel.Invoke method provides a convenient way to run any number of arbitrary statements concurrently. Just pass in an Action delegate for each item of work. The easiest way to create these delegates is to use lambda expressions.
The Parallel.Invoke method provides a simple way in which a number of tasks may be created and executed in parallel. As with other methods in the Parallel Task Library, Parallel.Invoke provides potential parallelism. If no benefit can be gained by creating multiple threads of execution the tasks will run sequentially.
To use Parallel.Invoke, the tasks to be executed are provided as delegates. The method uses a parameter array for the delegates to allow any number of tasks to be created. The tasks are usually defined using lambda expressions but anonymous methods and simple delegates may be used instead. Once invoked, all of the tasks are executed before processing continues with the command following the Parallel.Invoke statement. The order of execution of the individual delegates is not guaranteed so you should not rely on the results of one operation being available for one that appears later in the parameter array.
Exception Handling with Parallel.Invoke: In the case of Parallel.Invoke, it is guaranteed that every task will be executed. Each task will either exit normally or throw an exception. All of the thrown exceptions are gathered together and held until all of the tasks have stopped, at which point an AggregateException containing all of the exceptions is thrown. The individual errors can be found within the InnerExceptions property.
2.  Creating and Running Tasks Explicitly: If we need more control over parallel tasks, we can use the Task class. This allows us to explicitly generate parallel tasks. The code needed for explicit task creation is slightly more complex than that for Parallel.Invoke but the benefits outweigh this disadvantage.
A task is represented by the System.Threading.Tasks.Task class. A task that returns a value is represented by the System.Threading.Tasks.Task<TResult> class, which inherits from Task. The task object handles the infrastructure details, and provides methods and properties that are accessible from the calling thread throughout the lifetime of the task. For example, you can access the Status property of a task at any time to determine whether it has started running, ran to completion, was canceled, or has thrown an exception. The status is represented by a TaskStatus enumeration.
When you create a task, you give it a user delegate that encapsulates code that task will execute. Delegate can be expressed as a named delegate, an anonymous method, or a lambda expression. Lambda expressions can contain a call to a named method.
The Task class provides a wrapper for an Action delegate. The delegate describes the code that you wish to execute and the wrapper provides parallelism. A simple way to create a task is to use the constructor that has a single parameter, which accepts delegate that you wish to execute. Tasks do not execute immediately after being created. To start a task call its Start method.
3.  Many More….: Working with Task class is complex and big topic. In the above example I have given you some example and explanation. I cannot cover all related topic in this session; we need a separate session if you want to learn all the features and functionality. Here I am giving you a list of topic which you can take it as your task, study these topics and create some examples so that you will get better understanding: [MSDN Link: http://msdn.microsoft.com/en-us/library/dd537609(v=vs.100).aspx]
  • Waiting on Tasks: The System.Threading.Tasks.Task type and System.Threading.Tasks.Task<TResult> type provide several overloads of a Task.Wait and Task<TResult>.Wait method that enable you to wait for a task to complete. In addition, overloads of the static Task.WaitAll and Task.WaitAny method let you wait for any or all of an array of tasks to complete.
You can also have a look on Exception Handling if any exception is thrown during the execution of a task and Adding Timeouts for long running task.
  • Task Results: The Task<T> generic class inherits much of its functionality from its non-generic counterpart. Tasks are created using a delegate, often a lambda expression, started using the Start method and executed in parallel where it is efficient to do so. This return value can be accessed by reading the task's Result property.
  • Continuation Tasks: When you are writing software that has tasks that execute in parallel, it is common to have some parallel tasks that depend upon the results of others. These tasks should not be started until the earlier tasks, known as antecedents, have completed. Before the introduction of the Task Parallel Library (TPL), this type of interdependent thread execution would be controlled using callbacks.
The Task.ContinueWith method and Task<TResult>.ContinueWith method let you specify a task to be started when the antecedent task completes. The continuation task's delegate is passed a reference to the antecedent, so that it can examine its status. In addition, a user-defined value can be passed from the antecedent to its continuation in the Result property, so that the output of the antecedent can serve as input for the continuation.
You can also check some topic related to Using Task Results in Continuations, Exceptions Handling with Continuations Task, Creating Continuations with Multiple Antecedents, and Multiple Continuations of a Single Antecedent.
  • Nested Task: When user code that is running in a task creates a new task and does not specify the AttachedToParent option, the new task not synchronized with the outer task in any special way. Such tasks are called a detached nested task.
Tasks may be nested in this manner to many levels deep. The inner tasks are known as child tasks, of which there are two types. The first type is the detached child task; also known as nested tasks. The other type of child task is the attached child task, generally known simply as child tasks.
When you create nested tasks there is no link between a nested task and its parent. Nested tasks are completely independent, reporting a separate status and throwing their own exceptions.
  • Child Tasks: When user code that is running in a task creates a task with the AttachedToParent option, the new task is known as a child task of the originating task, which is known as the parent task. You can use the AttachedToParent option to express structured task parallelism, because the parent task implicitly waits for all child tasks to complete.
  • Canceling Tasks: The Task class supports cooperative cancellation and is fully integrated with the System.Threading.CancellationTokenSource class and the System.Threading.CancellationToken class, which are new in the .NET Framework version 4. Many of the constructors in the System.Threading.Tasks.Task class take a CancellationToken as an input parameter. Many of the StartNew overloads also take a CancellationToken.
You can create the token, and issue the cancellation request at some later time, by using the CancellationTokenSource class. Pass the token to the Task as an argument, and also reference the same token in your user delegate, which does the work of responding to a cancellation request.
4. What is Task ID? - Every task receives an integer ID that uniquely identifies it in an application domain and that is accessible by using the Id property. The ID is useful for viewing task information in the Visual Studio debugger Parallel Stacks and Parallel Tasks windows. The ID is lazily created, which means that it isn't created until it is requested; therefore a task may have a different ID each time the program is run.

No comments:

Post a Comment