Monday, 1 August 2011

Parallel programming using New TPL library in .Net 4.0

 
There days almost all computers are assembled with multicore processors, and the time is different from when you had simple single core processor processing single instruction at a time of your program. If you creating an application that has to be installed on multicore CPU machine, then application should be able to utilize as CPUs as it can to give the performance. But to develop such application you need to be quite skilled in multhithreaded programming techniques
With the .Net 4.0, you are provided with the brand new parallel programming language library called as TPL “Task Parallel Library”. Using the classes in System.Threading.Tasks namespace, you can build fine grained, scalable parallel code without having to work directly with threads.

The Task Parallel Library API
The primary class of the TPL is System.Threading.Tasks.Parallel. This class provides you a number of methods which allows you to iterate over a collection of data, (specifically which implements the IEnumerable<T>) in a parallel fashion. Main two static methods of this classes are Parallel.For() and Parallel.Foreach(), each of which defines numerous overloaded versions.

Lets create an small search application in WindowsForms:
Create a simple form like below.


Now click of Search Button write the code below:

  1. string path = "H:\\";

  1. private void btnSearch_Click(object sender, EventArgs e)
  2. {
  3.     DateTime startTime = DateTime.Now;
  4.     DateTime endTime;
  5.     string[] dirs = Directory.GetDirectories(path);
  6.     List<string> lstFiles = new List<string>();
  7.     foreach (string dir in dirs)
  8.     {
  9.         this.Text = "Searching " + dir;
  10.         try
  11.         {
  12.             lstFiles.AddRange(Directory.GetFiles(dir, "*.doc*", SearchOption.AllDirectories));
  13.         }
  14.         catch (Exception ex)
  15.         {
  16.             continue;
  17.         }
  18.     }
  19.  
  20.     endTime = DateTime.Now;
  21.     TimeSpan ts = endTime.Subtract(startTime);
  22.     MessageBox.Show("Search Complete!! \n Time elapsed:" + ts.Minutes + ":" + ts.Seconds + ":" + ts.Milliseconds);
  23. }

OUTPUT:


Now write the same logic using the Parallel.ForEach() function of System.Threading.Tasks
  1. private void btnSearch_Click(object sender, EventArgs e)
  2.         {
  3.             DateTime startTime = DateTime.Now;
  4.             DateTime endTime;
  5.             string[] dirs = Directory.GetDirectories(path);
  6.             List<string> lstFiles = new List<string>();
  7.  
  8.             Parallel.ForEach(dirs, dir =>
  9.             {
  10.                 try
  11.                 {
  12.                     lstFiles.AddRange(Directory.GetFiles(dir, "*.doc*", SearchOption.AllDirectories));
  13.                 }
  14.                 catch
  15.                 {
  16.                     // do nothing
  17.                 }
  18.             });
  19.  
  20.             endTime = DateTime.Now;
  21.             TimeSpan ts = endTime.Subtract(startTime);
  22.             MessageBox.Show("Search Complete!! \nFiles Found:" + lstFiles.Count + "\n Time elapsed:" + ts.Minutes + ":" + ts.Seconds + ":" + ts.Milliseconds);
  23.         }

OUPUT:


Little faster than the Previous one!

But here you must be facing a problem. Have you tried writing something during this operation on the UI. I don’t if you could do that cause UI was freezing because Main thread is waiting for the operation to complete that is executing in Synchronous manner.
Let use few more Powerful functionality of System.Threading.Tasks to make the UI interactive while work is being done.

Place all the code inside the a function say SearchDirectory().
  1. private void SearchDirectory()
  2. {
  3.     DateTime startTime = DateTime.Now;
  4.     DateTime endTime;
  5.     string[] dirs = Directory.GetDirectories(path);
  6.     List<string> lstFiles = new List<string>();
  7.  
  8.     Parallel.ForEach(dirs, dir =>
  9.     {
  10.         try
  11.         {
  12.             lstFiles.AddRange(Directory.GetFiles(dir, "*.doc*", SearchOption.AllDirectories));
  13.         }
  14.         catch
  15.         {
  16.             // do nothing
  17.         }
  18.     });
  19.  
  20.     endTime = DateTime.Now;
  21.     TimeSpan ts = endTime.Subtract(startTime);
  22.     MessageBox.Show("Search Complete!! \nFiles Found:" + lstFiles.Count + "\n Time elapsed:" + ts.Minutes + ":" + ts.Seconds + ":" + ts.Milliseconds);
  23.  
  24. }
To make the UI you can use async delegates but this time we’re going to use bit more
  1. private void btnSearch_Click(object sender, EventArgs e)
  2. {
  3.      //Start a task to process a file
  4.      Task.Factory.StartNew(() => SearchDirectory());
  5. }
easier approach than writing whole code to make Async call.
 
The Factory property of Task Class returns TaskFactory object. When you call its StartNew() method, you pass in an Action<T> delegate (Here lambda expression is hiding the expression). Now your UI will be able to receive input and will not block.
Note: I changed the search path from drive h:\ to c:\ so that i could get time write something thats why the time here is little different than the earlier output.
OUTPUT:




Including Cancellation Reqeust for background process:


The Parallel.Foreach() and Paralled.For() both support the cancellation through the Cancellation tokens. When you invoke the methods on Parallel, you can pass in a ParallelOptions object, which in turn contains a CalcellationTokenSource object.

To add this functionality in your application first of all, defina new private member variable in your form derived from class of time CancellationTokenSource named cancelToken:
  1. //Cancellation token for cancelling the invoke
  2.         private CancellationTokenSource cancelToken = new CancellationTokenSource();

To cancel the task just add an cancel button and write single line in it:
  1. cancelToken.Cancel();

Add Parallel option instance to store the Cancellation Token
  1. ParallelOptions parOpts = new ParallelOptions();
  2.             parOpts.CancellationToken = cancelToken.Token;
  3.             parOpts.MaxDegreeOfParallelism = Environment.ProcessorCount;
  4.  
  5.             try
  6.             {
  7.                 Parallel.ForEach(dirs, parOpts, dir =>
  8.                 {
  9.                     parOpts.CancellationToken.ThrowIfCancellationRequested();
  10.                     try
  11.                     {
  12.                         lstFiles.AddRange(Directory.GetFiles(dir, "*.doc*", SearchOption.AllDirectories));
  13.                     }
  14.                     catch
  15.                     {
  16.                         // do nothing
  17.                     }
  18.                 });
  19.  
  20.                 endTime = DateTime.Now;
  21.                 TimeSpan ts = endTime.Subtract(startTime);
  22.                 MessageBox.Show("Search Complete!! \nFiles Found:" + lstFiles.Count + "\n Time elapsed:" + ts.Minutes + ":" + ts.Seconds + ":" + ts.Milliseconds);
  23.             }
  24.             catch (OperationCanceledException ex)
  25.             {
  26.                 MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Warning);
  27.             }

OUTPUT:


Within the scope of looping logic, you make a call to ThrowIfCancellationRequested () on the token,which will ensure if user clicks the cancel button, all threads will stop and you will be notified via a runtime exception.
 
I hope you enjoyed reading this. Please comment if you liked it and spread the words about it.

No comments:

Post a Comment