Friday 1 August 2014

Parallel Programming 00

Parallel programming is exactly what it sounds like, doing things at the same time, luckily because of c# constructs namely the Task Parallel Library and the async/await magic it's a lot easier than it sounds as well.

A Tiny history lesson, before c# 4.0 we had to use Threads and the ThreadPool for asynchronous programming; this was hard. Now we have the Task Parallel library which made life much easier, after that the magical async/await keywords appeared. I say magical, because that's exactly what they are they're just syntactical fairy dust that actually turns into the task.run().ContinueWith.

Lets start with a console application, take a look at the following:

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Example00
{
    class Program
    {
        static void Main(string[] args)
        {
            var sw = new Stopwatch();
            sw.Start();
            var num0 = GetNumber(3);
            var num1 = GetNumber(4);
            sw.Stop();

            Console.WriteLine("{0} + {1} = {2} in {3}'s"num0, num1, num0 + num1, sw.Elapsed.Seconds);

            Console.WriteLine("Good Times");
        }

        public static int GetNumber(int num)
        {
            Task.Delay(1000 * num).Wait();
            return num;
        }
    }
}


pretty straight forward, we add two numbers in this case 3 and 4 with a delay equal in seconds to the numbers, so it takes 7 seconds for our calculation to complete and then we get Good Times displayed to us.

that's exactly what we expected but did we really need to wait for our calculation to complete before seeing the "Good Times" Message, it really has nothing to do with the calculation.

Let's use the Task Parallel library to do better:

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Example01
{
    class Program
    {
        static void Main(string[] args)
        {
            var num0 = 3;
            var num1 = 4;

            var sw = new Stopwatch();
            sw.Start();

            Task T = Task.Run(() =>
            {
                Console.WriteLine("Sum TaskId:{0}", Task.CurrentId);
                return GetNumber(num0) + GetNumber(num1);
            }).ContinueWith(tr =>
            {
                sw.Stop();
                Console.WriteLine("\nContinueWith TaskId:{0} ", Task.CurrentId);
                Console.WriteLine("{0} + {1} = {2} in {3}'s", num0, num1, tr.Result, sw.Elapsed.Seconds);
            });

            Console.WriteLine("Good Times");
            Task.WaitAll(T);
        }


        public static int GetNumber(int num)
        {
            Console.WriteLine("GetNumber({1}) TaskId:{0} ", Task.CurrentId, num);
            Task.Delay(1000 * num).Wait();
            return num;
        }
    }
}


This time we print the "Good Times" message immediately and calculate our total in a separate task.


but as you can see we only have two tasks in this modification, one to get our numbers synchronously and add them up, then a second task to display our results.

We can do better lets start up two more Tasks inside of our sum task to get the numbers separately and shave some time off our calculation time.

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Example02
{
    class Program
    {
        static void Main(string[] args)
        {
            var num0 = 3;
            var num1 = 4;

            var sw = new Stopwatch();
            sw.Start();

           
            Task T = Task.Run(() =>
            {
                Console.WriteLine("Sum TaskId:{0}", Task.CurrentId);
                var t0 = Task<int>.Run(() => GetNumber(num0));
                var t1 = Task<int>.Run(() => GetNumber(num1));

                return t0.Result + t1.Result;
            }).ContinueWith(tr =>
            {
                sw.Stop();
                Console.WriteLine("\nContinueWith TaskId:{0} ", Task.CurrentId);
                Console.WriteLine("{0} + {1} = {2} in {3}'s", num0, num1, tr.Result, sw.Elapsed.Seconds);
            });

            Console.WriteLine("Good Times");
            Task.WaitAll(T);
        }


        public static int GetNumber(int num)
        {
            Console.WriteLine("GetNumber({1}) TaskId:{0} ", Task.CurrentId, num);
            Task.Delay(1000 * num).Wait();
            return num;
        }
    }
}

not too bad, we created a sum task that retrieves our two number in parallel using two more task, then adds them up and prints them to the screen in a fourth task. As before our "Good Times" message is printed to the screen instantly.

now lets try the same thing but using the async/await keywords.

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Example03
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<int,int> calculate = async (num0, num1) => {
                var sw = new Stopwatch();
                sw.Start();

                int total = await Task.Run<int>(() => {
                    Console.WriteLine("Sum TaskId:{0}", Task.CurrentId);

                    var t0 = Task<int>.Run(() => GetNumber(num0));
                    var t1 = Task<int>.Run(() => GetNumber(num1));

                    return t0.Result + t1.Result;
                });

                sw.Stop();

                Console.WriteLine("{0} + {1} = {2} in {3}'s", num0, num1, total, sw.Elapsed.Seconds);
            };

            calculate(3, 4);

            Console.WriteLine("Good Times");
            Console.ReadKey();
        }

        public static int GetNumber(int num)
        {
            Console.WriteLine("GetNumber({1}) TaskId:{0} ", Task.CurrentId, num);
            Task.Delay(1000 * num).Wait();
            return num;
        }
    }
}


pretty straight forward, one thing that may through you off is the use of an action inside the main, this is because entry points cannot be asynchronous.

the above being a bit convoluted, here's a final example to really demonstrate that Task.Run().ContinueWith is the same thing as async/await

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Example04
{
    class Program
    {
        static void Main(string[] args)
        {
            var num0 = 3;
            var num1 = 4;
            var sw = new Stopwatch();

            sw.Start();
            var result0 = SlowAdd(num0, num1);
            sw.Stop();
            Console.WriteLine("TPL: {0} + {1} = {2} in {3}'s", num0, num1, result0, sw.Elapsed.Seconds);

            sw.Restart();
            var result1 = SlowAddAsync(num0, num1).Result;
            sw.Stop();
            Console.WriteLine("ASYNC: {0} + {1} = {2} in {3}'s", num0, num1, result1, sw.Elapsed.Seconds);
        }

        static int SlowAdd(int a, int b)
        {
            return GetNumber(3) + GetNumber(4);
        }

        async static Task<int> SlowAddAsync(int a, int b)
        {
            return await GetNumberAsync(a) + await GetNumberAsync(b);
        }

        static int GetNumber(int num)
        {
            //start a task
            return Task.Run(
                //wait the number * seconds
                () => Task.Delay(1000 * num))
                //once waiting task complete continue
                    .ContinueWith(
                //return the number
                        tr => num).Result;
        }

        async static Task<int> GetNumberAsync(int num)
        {
            await Task.Delay(1000 * num);
            return num;
        }
    }
}

above, the two functions to pay attention to are GetNumber and GetNumberAsync, they both do the same thing, they take in a number wait an equal amount of seconds then pass the number back. In fact they will compile down to roughly the same code it's just that one is much easier to read than the other.