Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Synchronization Domains and Task Parallel Library in C#

5.00/5 (1 vote)
29 May 2012CPOL3 min read 34.3K   694  
Two relatively new ways of developing multi-threaded applications are compared: Synchronization Domains and the Task Parallel Library

Introduction

When it comes to multithreaded application development, there are many ways to skin that cat, many of which I have experimented with and used. In this article, I would like to discuss two relatively new ways of handling this concept in the C# programming language: synchronization domains and task parallel library (TPL). The former I have learned from Juval Löwy's book "Programming .NET Components"1 , and the other from Frank Hubbell's presentation2 of TPL at a Visual Developers of Upstate New York meeting.

Multithreading is important in many ways, among which is separation of concerns, a phrase from Chris Reade's Elements of Functional Programming book3, that explains how avoiding the details of sequencing makes it easier to maintain an application. It's also important in maximizing CPU utilization.

The human body, as a metaphor, is multithreaded as we can move multiple limbs at once while talking, seeing, and hearing. What is amazing about synchronization domains is how well it interlaces multiple threads by also bringing the concept onto a higher level of abstraction allowing the programmer to avoid explicitly coding locks, signals, and other synchronization concepts.

Synchronization Domain

Programming with synchronization domains using the language attribute [Synchronization] to a class that is derived from ContextBoundObject base class has amazing results in the fairness of how the threads are invoked by the CPU. By fairness, I mean equal time is devoted to each thread. Bear in mind, however, this method is not perfect especially in the beginning and end of running the code for understandable reasons. For absolute precision the fundamentals, such as using a monitor, would be required as in robotics for example.

An entire class is devoted as a shared source:

C#
[Synchronization]
public class MySharedResource : ContextBoundObject
{
    public MySharedResource()
    { }

    public void DisplayThreadInfo(int id)
    {
        Console.Write(string.Format("{0}", id));
    }
}

This shared resource, or MySharedResource class, can then be used in another class that will be the focus of a worker thread. The magic of synchronization domain is in the way the shared resource is controlled without any pain, which is the single most dangerous part of multi-threaded applications:

C#
public class MyClass
{
    private MySharedResource mMySharedResource = null;
    private int mId = 0;
    public MyClass(MySharedResource MySharedResource, int id)
    {
        mMySharedResource = MySharedResource;
        mId = id;
    }
    public void ShowMessage()
    {
        Thread currentThread =
            Thread.CurrentThread;
        int threadID = currentThread.ManagedThreadId;
        for (int i = 0; i < 1000; i++)
        {
            //Monitor.Enter(MyLock);
            mMySharedResource.DisplayThreadInfo(mId);
            Count++;
            //Monitor.Wait(MyLock);
            //Monitor.Exit(MyLock);
        }
    }
}

The monitor code is commented out above to illustrate the spectacular "fairness" of synchronization domain. Take out the comments to the monitor lines to make it absolutely fair for each thread--i.e., each thread gets exactly the same CPU time slice.

The worker threads are created easily inside an app as in this console app:

C#
static void Main(string[] args)
{
    Thread currentThread =
        Thread.CurrentThread;
    int threadID =
        currentThread.ManagedThreadId;
    string threadName = "Main UI Thread";
    currentThread.Name = threadName;
    MySharedResource mySharedResource = new MySharedResource();

    /// Thread 1 //////////////////////////////////////////////
    MyClass obj = new MyClass(mySharedResource, 1);
    ThreadStart threadStart = obj.ShowMessage;
    Thread workerThread = new Thread(threadStart);
    ///////////////////////////////////////////////////////////
    /// Thread 2 //////////////////////////////////////////////
    MyClass obj2 = new MyClass(mySharedResource, 2);
    ThreadStart threadStart2 = obj2.ShowMessage;
    Thread workerThread2 = new Thread(threadStart2);
    ///////////////////////////////////////////////////////////
    /// Thread 3 //////////////////////////////////////////////
    MyClass obj3 = new MyClass(mySharedResource, 3);
    ThreadStart threadStart3 = obj3.ShowMessage;
    Thread workerThread3 = new Thread(threadStart3);
    ///////////////////////////////////////////////////////////
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();
    workerThread.Start();
    workerThread2.Start();
    workerThread3.Start();
    while (workerThread.IsAlive
        || workerThread2.IsAlive
        || workerThread3.IsAlive)
    {
        if (Count == MaxThreads)
        {
            Count = 0;
        }
    }
    workerThread.Join();
    workerThread2.Join();
    workerThread3.Join();
    stopWatch.Stop();
    Console.WriteLine("\nExiting application.");
    TimeSpan ts = stopWatch.Elapsed;
    Console.WriteLine("{0:.} ms", ts.TotalMilliseconds);
    Console.WriteLine("press RETURN to exit");
    Console.ReadLine();
}

Creation of threads above is the same as we've been coding for years, but with less worry on shared resources thanks the synchronized domains. The output looks amazingly interlaced between the threads:

Image 1

Task Parallel Library

There is a new method now offered by Microsoft to make multi-threading easier on the other side: instead of the shared resource being manipulated, the worker threads are created in a special way--an intuitive way. This is where the Task Parallel Library comes into play.

The same functionality as before can be written as follows:

C#
namespace TaskParallelLibraryExample
{
    public class MySharedResource
    {
        public void Task(int id)
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.Write(string.Format("{0}", id));
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MySharedResource mySharedResource = new MySharedResource();
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            Parallel.Invoke(
                () => mySharedResource.Task(1),
                () => mySharedResource.Task(2),
                () => mySharedResource.Task(3));
            Console.WriteLine("\nExiting application.");
            TimeSpan ts = stopWatch.Elapsed;
            Console.WriteLine("{0:.} ms", ts.TotalMilliseconds);
            Console.WriteLine("press RETURN to exit");
            Console.ReadLine();
        }
    }
}

The output is similar to the traditional way of creating multiple threads and the CPU time slices given to each thread is seemingly random. Again, shared resource is well managed. Because the threads are not interlaced as neatly as synchronized domains, its performance is understandably faster (if you take out the [Synchronized] from the first code, it will be just as fast, but not synchronized):

Image 2

Points of Interest

There are many ways to develop software with multiple threads. In this article, a comparison study between Synchronization Domain and Task Parallel Library in a simple manner will hopefully be useful to those who are new to this kind of programming. Both types are attached to this article as Visual Studio 2010 solutions. I also added a synchronized version of the TPL example, which has a performance similar to the synchronized domain, but the code has more if statements than I would like.

References

  • [1] Löwy, Juval. Programming .Net Components, 2nd Edition. O'Reilly. August 3, 2005.
  • [2] Hubbell, Frank presents Microsoft’s Task Parallel Library (TPL) http://www.vduny.org/pastmeet.asp August 25, 2011
  • [3] Reade, Chris. Elements of Functional Programming. Addison-Wesley. 1989.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)