Learn Parallel Programming in .NET

We are going to create a process for opening Notepad and wait until the Notepad is closed. The program tracks when the Notepad is closed.

Problem statement

Execution of processes in .NET using multithreading concept. Until .NET 4.0 the only possibility was using Thread or BackgroundWorker and handling the processes in different threads and executing them. It needs a lot of code and effort. From .NET 4.0 Microsoft invested more time in concentrating on Parallel Programming to leverage multiprocessor architectures. The new parallel programming constructs are.

  • Parallel LIN
  • Parallel Class
  • Task Parallelism
  • Concurrent Collections
  • SpinLock and SpinWait.

This article does not discuss all the items above. But we will be concentrating on Task Parallelism and Concurrent Collections today.

Task Parallelism is the lowest-level approach to parallelization. The classes that come under Task Parallelism are grouped in the namespace System.Threading.Tasks and this namespace contain the following.

Class Purpose
Task For managing a unit of work
Task<TResult> For managing a unit of work with a return value
TaskFactory For creating tasks
TaskFactory<TResult> For creating tasks and continuations with the same type
TaskCompletionSource For manually controlling a task's workflow

Everyone should have studied the Producer-Consumer algorithm. If you are interested in learning more about that you can refer to the following article.

http://blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx

In this article, the author beautifully explains the BlockingCollection<T> class and how it solves the Producer-Consumer problem. In our example, we are going to use the same class.

Before going to the actual program, learn the following terms used in our programming.

  • Task.Factory.StartNew: This starts a new task for each consumer.
  • Collection.CompleteAdding(): This indicates that the collection has completed adding and it will not allow any more addition of items and attempts to remove from the collection will not wait when the collection is empty.
  • Task.WaitAll: You can wait on multiple tasks at once - via the static methods Task.WaitAll (Wait for the specific tasks to finish).
  • Collection.TryTake: Attempts to remove an item from the BlockingCollection<T>.
  • p.Start(): Starts a process and associates it with a System.Process component.
  • p.WaitForExit(): Tells the Process component to wait indefinitely for the associated process to exit.
  • Collection.IsAddingCompleted: Whether this collection has been marked as complete for adding.

For example, I am taking only 5 instances of Notepad.exe processes that spawn concurrently, and their exit is tracked and displayed in a console application. This is a simple example, but a good one to understand the Parallel Programming introduced in . NET.

Here is my code

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Threading.Tasks;
class Program {
    static readonly BlockingCollection<string> Collection = new BlockingCollection<string>();
    private static int _count = 0;
    static void Main() {
        const int maxTasks = 5;
        var tasks = new List<Task> {
            Task.Factory.StartNew(() => {
                for (var i = 0; i < 5; i++) {
                    Collection.Add(i.ToString(CultureInfo.InvariantCulture));
                }
                Console.WriteLine("Spawning multiple processes completed. Now wait and see until all the jobs are completed.");
                Collection.CompleteAdding();
            }),
        };
        for (var i = 0; i < maxTasks; i++) {
            tasks.Add(Task.Factory.StartNew(UserTasks(i)));
        }
        Task.WaitAll(tasks.ToArray());
    }
    static Action UserTasks(int id) {
        return () => {
            while (true) {
                string item;
                if (Collection.TryTake(out item, -1)) {
                    using(Process p = new Process()) {
                        p.StartInfo.FileName = "notepad.exe";
                        p.Start();
                        p.WaitForExit();
                        var exitCode = p.ExitCode;
                        Console.WriteLine(exitCode == 0 ? "{0} exited successfully!!!" : "{0} exited failed!!!", p.Id);
                    }
                } else if (Collection.IsAddingCompleted) {
                    break;
                }
            }
            Console.WriteLine("Consumer {0} finished", id);
            _count = _count + 1;
            if (_count == 4) {
                Console.ReadLine();
            }
        };
    }
}

In this program

  • We have initially created an instance of the BlockingCollection<String> object.
  • We have created a new Task list and added new tasks using Task.Factory.StartNew.
  • Then we added the string objects to the BlockingCollection object.
  • Once the string objects have been added we set the completion by CompletionAddding() method.
  • We have written an Action (Functional Programming) called UserTasks and added code for spawning a process.

Now let us run the program. Click on F5. You will see that it has opened 5 Notepads in your application and you will see a message that the spawning of multiple processes has been completed.

Multiprocessing

Now close one of the opened Notepads and you will see the following message in the console.

open notepad

Now close each of the Notepad windows one by one and observe the console. When you have closed all the Notepad windows you should see the final output as below.

Exited multiple

Hope you liked this simple program. Instead of Notepads, in real-world projects, we may need to spawn multiple jobs or tasks that need to be running asynchronously and concurrently and always reporting the status back to the main thread. You can extend this application from simple to complex tasks.

References: MSDN site and Threading in C# by Albahari.

Threading in C# is an excellent work by Albahari that would give you more insight into the Multi-Threading support in .NET and I would recommend every programmer either to learn or revise the concepts of threading.


Similar Articles