Improve your Application Performance with .NET Framework 4.0

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

The processors we use today are quite different from those of just a few years ago, as most processors today provide multiple
cores and/or multiple threads. With multiple cores and/or threads we need to change how we tackle problems in code.
Yes we can still continue to write code to perform an action in a top down fashion to complete a task. This apprach will
continue to work; however, you are not taking advantage of the extra processing power available. The best way to take
advantage of the extra cores prior to .NET Framework 4.0 was to create threads and/or utilize the ThreadPool. For many developers utilizing Threads or the ThreadPool can be a little daunting. The .NET 4.0 Framework drastically simplified the process of utilizing the extra processing power through the Task Parallel Library (TPL).

The TPL is broken down into three different type of parallelism: Data Parallelism, Parallel LINQ (PLINQ) and Task
Parallelism. These three different types of parallelism are designed to make it easier to tackle some of the common areas within
your applications where you can take better advantages of the processor. Before we can take advantage of TPL we first need
to add the base using statement to the class as shown below.

  Using System.Threading.Tasks;

Data Parallelism

One of the more common areas where you can take advantage of parallel processing is related to data processing. Often within code
you have a need to perform a For loop or a For Each loop on a set of data. These loops execute serially and
can be a bottleneck to performance. As the size of the data set being executed upon increases the impact on performance can become
quite noticeable. Data Parallelism essentially allows you to modify your For and For Each loops to execute in
parallel. The following code snippet shows a simple traditional for loop.

  for (int i = 0; i < 10; i++)
  {
      //Perform Work
  }

  This code can be easily modified to use TPL, perform the same function and take advantage of the processor as shown below:

  Parallel.For(0, 10, delegate(int i)
  {
      //Perform Work
  });

As you can see this code snippet does not use the for keyword instead it uses the Parallel.For method. The
Parallel.For method accepts 3 parameters, the start number, the ending number and the Action. The Action in this case is
an inline delegate. It is also entirely possible to perform the Parallel.For operation on a method instead of an inline
delegate as shown in the following snippet.

Parallel.For(0, 10, ForWork);

static void ForWork(int i)
{
    //Perform Work
}
    

This performs the same function as the previous snippet; however, it allows you to externalize work performed by the delegate to a
separate method. As you can see converting For loops to better take advantage of the processor is a relatively simple
process. The For Each loops is really a specialized version of the For loop designed to iterate through a
list. As such, the conversion is similar to a For Each loops as shown in the following snippet:

  //Create a list of Customer
  List Customers = new List();

  //Serial foreach loop
  foreach (Customer c in Customers)
  {
     //Perform Work on Customer
  }

  //Parallel ForEach loop
  Parallel.ForEach(Customers, delegate(Customer c)
  {
     //Perform Work on Customer
  });

The Parallel.ForEach works as you would expect, accepting a single parameter of the List and the Action.
It is important to note that while it is easy in theory to convert to use the parallel loops we do need to give some consideration to
what we are performing within Action being executed. For instance, you need to watch out for operations which are not thread safe,
such as inserting into a list. Operations which are not thread safe need special attention. In order to prevent collisions against the
other threads you should use the lock keyword as shown below:

  lock (Customers)
  {
     Customers.Add(c);
  }

When you are locking an object such as a List you should only include those statements which are essential. The lock statement
essentially serializes access to the Customer List in the example above. Thus if you serialize a significant portion of your code
within the loop you cancel out the advantages.

Parallel LINQ (PLINQ)

LINQ at its lowest level is essentially a series of operations performed within a For Each loop. It would make sense to that LINQ can be altered to take advantage of the Data Parallelism from above. Before we talking about enabling PLINQ we
will start with the following simple query:

var query = from c in Customers
      where c.Name.StartsWith("Ch")
      select c;

In this example we have a very simple LINQ query to scan through the list of Customers and return all items where Name starts
with "Ch". To turn on the PLINQ operation we make a small change to this query as shown below:

var query = from c in Customers.AsParallel()
        where c.Name.StartsWith("Ch")
        select c;

As you can see in this snippet by adding AsParallel() onto the end of the IEnumerable List we can enable the parallel
operation. There are other extensions we can apply to the IEnumerable source to preserve the order AsOrdered(), perform
aggregations Aggregate() as well as other operations. Keep in mind that PLINQ is not a performance solution to all LINQ
operations. LINQ to SQL and LINQ to Entities operations are performed on the respective database and as such PLINQ isn’t
going to be able to affect those queries.

Task Parallelism

Task Parallelism allows you to create a task and launch that task asynchronously. In its simplest form we can create a
single task and execute the operation as shown below:

  Task.Factory.StartNew(delegate()
  {
     //Perform Work
  });

This example creates a single task and executes it; however, since it is only a single task it is not terribly useful.
It would be far more useful to provide a list of tasks you need to accomplish in parallel and wait for the execution to complete
as is the case in the following snippet:

  List Tasks = new List()
  {
     Task.Factory.StartNew(delegate()
     {
        //Perform Work
     }),
     Task.Factory.StartNew(delegate()
     {
        //Perform Work
     })
  };

Here we are creating a tasks list, added 2 tasks and starting those tasks. Then to make it more useful we use the
Task.WaitAll method to pause the current thread until the provided list of tasks complete.

Conclusion

The Task Parallel Library provides several methods to make it easier to take advantage of additional processing power;
however, the TPL is not a solution to all performance problems. These techniques should only be implemented when the code
being executed takes above a noticeable amount of time to execute. Meaning that it is not a good idea to implement these
techniques for operations that execute very quickly (due to the execution overhead). If you know the operation you are
performing will take sometime to execute, then you should plan on taking advantage of the TPL within your code. The TPL can
also make it easier for developers to implement code which will execute in the background/asynchronously without the learning
curve of creating separate Threads.

Related Articles

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read