Improve your Application Performance with .NET Framework 4.0

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



About the Author

Chris Bennett

Chris Bennett is a Manager with Crowe Horwath LLP in the Indianapolis office. He can be reached at chris.bennett@crowehorwath.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • One of the most foolproof ways for an online system to confirm, "Is it really you?" is by adding two-factor authentication. Modern two-factor solutions have evolved to support new, complex technology models that change how we use data, including cloud computing and bring-your-own-device (BYOD). However, not all two-factor authentication solutions are created equal. This guide walks through some of the key area of differentiation between two-factor authentication solutions and provides some concrete criteria …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds