There days nearly all computers are assembled with multicore processors. Now the time is different from when you had simple single-core processors processing a single instruction at a time of your program. If you are creating an application that has to be installed on a multicore CPU machine, then the application should be able to utilize as many CPUs as it can to provide improved performance. But to develop such an 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 "Task Parallel Library" (TPL). Using the classes in the 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.
Let's create a small search application in WindowsForms:
Create a simple form like below:
Now for the click event handler of the Search Button write the code below:
private void btnSearch_Click(object sender, EventArgs e)
{
DateTime startTime = DateTime.Now;
DateTime endTime;
string[] dirs = Directory.GetDirectories(path);
List<string> lstFiles = new List<string>();
foreach (string dir in dirs)
{
this.Text = "Searching " + dir;
try
{
lstFiles.AddRange(Directory.GetFiles(dir, "*.doc*", SearchOption.AllDirectories));
}
catch(Exception ex)
{
continue;
}
}
endTime = DateTime.Now;
TimeSpan ts = endTime.Subtract(startTime);
MessageBox.Show("Search Complete!! \n Time elapsed:"+ts.Minutes+":"+ts.Seconds+":"+ts.Milliseconds);
}
OUTPUT:
Now write the same logic using the Parallel.ForEach() function of System.Threading.Tasks
private void btnSearch_Click(object sender, EventArgs e)
{
DateTime startTime = DateTime.Now;
DateTime endTime;
string[] dirs = Directory.GetDirectories(path);
List<string> lstFiles = new List<string>();
Parallel.ForEach(dirs, dir =>
{
try
{
lstFiles.AddRange(Directory.GetFiles(dir, "*.doc*", SearchOption.AllDirectories));
}
catch
{
// do nothing
}
});
endTime = DateTime.Now;
TimeSpan ts = endTime.Subtract(startTime);
MessageBox.Show("Search Complete!! \nFiles Found:"+lstFiles.Count+"\n Time elapsed:"+ts.Minutes+":"+ts.Seconds+":"+ts.Milliseconds);
}
OUPUT:
That is a 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 the Main thread is waiting for the operation to complete that is executing in a Synchronous manner.
Let us now use a few more powerful functionalities of System.Threading.Tasks to make the UI interactive while work is being done.
Place all the code inside a function; say SearchDirectory().
private void SearchDirectory()
{
DateTime startTime = DateTime.Now;
DateTime endTime;
string[] dirs = Directory.GetDirectories(path);
List<string> lstFiles = new List<string>();
Parallel.ForEach(dirs, dir =>
{
try
{
lstFiles.AddRange(Directory.GetFiles(dir, "*.doc*", SearchOption.AllDirectories));
}
catch
{
// do nothing
}
});
endTime = DateTime.Now;
TimeSpan ts = endTime.Subtract(startTime);
MessageBox.Show("Search Complete!! \nFiles Found:" + lstFiles.Count + "\n Time elapsed:" + ts.Minutes + ":" + ts.Seconds + ":" + ts.Milliseconds);
}
To make the UI you can use async delegates but this time we're going to use a little easier approach than writing the whole code to make an Async call.
private void btnSearch_Click(object sender, EventArgs e)
{
//Start a task to process a file
Task.Factory.StartNew(() => SearchDirectory());
}
The Factory property of the Task Class returns a 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.
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:
//Cancellation token for cancelling the invoke
private CancellationTokenSource cancelTOken = new CancellationTokenSource();
To cancel the task just add a cancel button and write a single line in it:
cancelToken.Cancel();
Add the Parallel option instance to store the Cancellation Token:
ParallelOptions parOpts = new ParallelOptions();
parOpts.CancellationToken = cancelToken;
parOpts.MaxDegreeOfParallelism = Environment.ProcessorCount;
try
{
Parallel.ForEach(dirs, dir =>
{
parOpts.CancellationToken.ThrowIfCancellationRequested();
try
{
lstFiles.AddRange(Directory.GetFiles(dir, "*.doc*", SearchOption.AllDirectories));
}
catch
{
// do nothing
}
});
endTime = DateTime.Now;
TimeSpan ts = endTime.Subtract(startTime);
MessageBox.Show("Search Complete!! \nFiles Found:" + lstFiles.Count + "\n Time elapsed:" + ts.Minutes + ":" + ts.Seconds + ":" + ts.Milliseconds);
}
catch (OperationCanceledException ex)
{
MessageBox.Show(ex.Message, "Message", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
Within the scope of the looping logic, you make a call to ThrowIfCancellationRequested () on the token, which will ensure that if the 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 word about it.