Parallelizing Windows Applications in .NET 4.0
Join the DZone community and get the full member experience.
Join For FreeTask Parallel Library (TPL) is designed to take advantage of multicore processors in user code easily. It is shipped with Visual Studio 2010 as part of .NET Framework 4.0. It was previously available as Parallel Extensions which could be used with Visual Studio 2008 applications, too. TPL gives you the convenience to turn existing sequential code into parallel branches, which may result into significant performance boost since all the available processors are being utilized. Although ThreadPool.QueueUserWorkItem is still there and can serve you the same as before, TPL is a more organized library which has richer APIs that support waiting, continuations, better exception handling, and so on. It is available at System.Threading.Tasks namespace.
Parallel Loops
Parallel.ForEach is probably the simplest form of parallelism. It may iterate through an IEnumerable in parallel fashion. However, whether it is parallel or not, it will call an Action with the current collection item as a parameter. Here is an example:
var magicString = string.Empty;
var list = new List<string> {".NET", "Zone"};
Parallel.ForEach(list, oneString =>
{
magicString += oneString;
});
Remember, parallelism is not guaranteed. TPL contains sophisticated algorithms which automatically adapts to the workload according to the machine configuration. However, whether you have a single processor or not, you can safely use TPL as it was built for running on such processors as well. From the given example, it is quite clear that parallelism we wanted to achieve for just a simple List<string> is overkill. Therefore, it is always wise to use when there is a necessity or chance to obtain improvement when run in parallel.
Parallelizing Tasks
TPL made task parallelization super convenient. It offers Task approach of parallelism, instead of Thread. Task wraps Thread and provide with more meaningful and necessary methods. You could obviously write the following code, spawn a thread into thread pool and achieve parallelism in previous versions of .NET:
static void Main()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
}
private void DoWork(object info)
{
}
Using TPL, it can be written like below:
static void Main()
{
Task.Factory.StartNew(() => DoWork());
}
private void DoWork()
{
}
This basically does the same, queues a thread to the worker thread pool, but in more convenient way. In order to obtain a reference to the task and play with it, we could assign it to a variable.
var task = Task.Factory.StartNew(() => DoWork());
The Task class has quite a few properties and methods to make our life in parallel world real easy. If reference is not all that necessary, we could use Parallel.Invoke, too. This method takes one or more Actions, executes them in parallel.
Parallel.Invoke(() => DoFirstWork(), () => DoSecondWork(), () => DoNWork());
You can also chain one parallel task to another using ContinueWith.
Task.Factory.StartNew(() => DoFirstWork()).ContinueWith((t) => DoSecondWork()).ContinueWith((t) => DoNWork());
Main advantage of Task over Thread approach is, as I have mentioned before, TPL contains very sophisticated algorithms which adapts to workload and it has its own calculation to maximize resource utilization to achieve the best performance possible. So, we are off the hook from creating and managing threads by ourselves and pondering whether it is going to be fruitful for our particular needs or not.
Parameterized Tasks
Thread management in earlier versions of Visual Studio is huge pain. You needed to keep track of the threads’ statuses almost manually. You had to pass parameters as object. No strongly typed objects could be passed, so type castings were in places. Here’s an example:
static class Program
{
private static int output = 0;
private static ManualResetEvent resetEvent = new ManualResetEvent(false);
private static void DoWork(object info)
{
output = (int)info + 100;
resetEvent.Set();
}
static void Main()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), 20);
WaitHandle.WaitAll(new WaitHandle[] { resetEvent });
Console.WriteLine(output);
}
}
The same can be achieved by the following using TPL. See how easy and more C#-ish it looks like:
static class Program
{
private static int DoWork(int info)
{
return info + 100;
}
static void Main()
{
var task = Task<int>.Factory.StartNew(() => DoWork(20));
task.Wait();
Console.WriteLine(task.Result);
}
}
The Task class takes the type of return value while calling the delegate. Then we have waited until the thread is done and returned the value. The result can be found by task.Result.
Updating UI Has Never Been So Elegant
Responsive UI is one of the most important attributes of a good application. Blocking UI thread for a background operation to complete, is a very bad practice, should be avoided at any cost and may result in Not Responding application. There is no special offering from TPL to make it easier, but its elegant style of handling asynchronous operations, makes it easy to write and understand such code. The following code starts a background thread, and then iterates the collection in parallel fashion, and after each iteration it updates the progress bar.
private void Form1_Load(object sender, EventArgs e)
{
var range = Enumerable.Range(0, 100);
Task.Factory.StartNew(() =>
{
Parallel.ForEach(range, i =>
{
progressBar.Invoke((Action) delegate { ReportProgress(i); });
Thread.Sleep(100);
});
});
}
private void ReportProgress(int value)
{
progressBar.Value = value;
}
Starting a new thread has allowed us to complete the operation in background. We have used Invoke method to let the ReportProgress method to access UI and update the progress bar. Be careful with the Parallel.For kind of methods inside UI thread. Never run them without starting a new thread. It may result in state corruption, unexpected exceptions and so on.
Exception Handling
Handling exceptions is different and more efficient than before. Exceptions thrown in the tasks, are combined into one AggregateException instance, which is propagated to the thread that joins the task. You can loop through the InnerExceptions property of it, find out exactly what happened there during the execution, and based on the exceptions you can decide next course of action.
The following obviously will not work, because the threads will not be running in this thread:
try
{
Task.Factory.StartNew(() =>
{
// Do stuffs.
});
}
catch (Exception e)
{
// Won't work
}
We can only expect the exceptions to come when the tasks join back, like in Wait or when we attempt to access task.Result:
var task = Task.Factory.StartNew(() =>
{
throw new ArgumentNullException();
});
try
{
task.Wait();
}
catch (AggregateException ae)
{
// Will work
foreach(var exception in ae.InnerExceptions)
{
if(exception is ArgumentNullException)
{
}
else if(exception is IndexOutOfRangeException)
{
}
else
{
}
}
}
Task Parallel Library is an excellent collection of classes for almost all the parallelism needs you may encounter while developing applications in .NET. You can expect the same behavior while you debug. You can set breakpoints as usual and step through. Even if you are developing for single processor, TPL keeps you one step ahead and makes your application ready for multiprocessor machines too.
Opinions expressed by DZone contributors are their own.
Comments