Monday, 30 January 2012

Introduction to TPL part 2 - Task

We have understood the background of the Task parallel library in the previous post. Now we need to concentrate on the building blocks of the library namely Task, Task scheduler, Blocking collection and the PLINQ.

Each of the TPL block has description of the features and example(s) associated with it. In this article I would like to discuss about the “Task” in this post.

Task

Coming right from the world of System.Threading.Thread a task looks little different. A TPL Task is nothing but a well wrapped Thread and having good infrastructure. TPL is still wrapped around the same namespace. TPL Task can be found in the namespace Syste.Threading.Tasks. TPL task has same ability to of a Thread object and been extended.

The below code shows how a thread and a TPL task can be used in typical scenarios



Code 1
using System;
using System.Threading;
using System.Threading.Tasks;

namespace TPLExample2
{
    class Program
    {
        public static void ThreadMethod()
        {
            Thread.Sleep(300);
            Console.WriteLine("Hello from thread1: ");
        }

        private static string TaskMethod(object state)
        {
            Console.WriteLine(state.ToString());
            return "hello return value from task1";
        }

        static void Main(string[] args)
        {
            ThreadStart s = new ThreadStart(ThreadMethod);
            // create thread 1
            System.Threading.Thread thread1 = new Thread(s);

            Console.WriteLine("Init Thread1");

            // start thread 1
            thread1.Start();

            // create and init task1
            Task task1 = Task.Factory.StartNew(
                new Func(TaskMethod), "Parameter to the Task1 Method");

            Console.WriteLine("Waiting for thread 1 to complete");

            //polling wait method to wait for thread1 to complete
            while (thread1.ThreadState != ThreadState.Stopped)
                Thread.Sleep(500);

            Console.WriteLine("Waiting for Task1 to complete");
            //In-built method to wait for task1 to complete
            Task.WaitAll(task1);

            // retrieve the return value to the calling thread
            Console.WriteLine("Task1 Completed, Result = " + task1.Result
                + "\r\n\r\nHit any key to exit....");

            Console.ReadLine();
        }
    }
}
In the above code is a common comparison between a thread and a TPL task. You can note that the creation of a thread and a task are almost similar. But when we look into the TPL task, it is more flexible and developer friendly. The method Task.Factory.StartNew will start the new task. This method actually creates a new thread from the thread pool. You can also observe that Task.WaitAll() method will block the calling thread till the specified tasks are complete. Note that this method does not take the Thread object as a parameter but only a “Task” object. Note: - The Task.WaitAny() method takes the one or more task objects as parameter. If any of the Tasks in the parameter completes, it will unblock the calling thread. In .Net v4.0 there are about 5 method overloads available. These overloads are similar to that of Task.WaitAll() method. Asynchronous programming is about making the UI of the application free for user actions when a particular work is going one. For E.g. when the user clicks on a button the UI is still available for other operations. The clicked button may be disabled till the operation is complete based on specific requirements. The common Async pattern uses Control.BeginInvoke(delegate) and Control.EndInvoke(). This pattern can be understood in detail from here http://msdn.microsoft.com/en-us/library/2e08f6yc(v=vs.100).aspx We will take a look at other examples where one can know different aspects of creation of tasks. Code 2
using System;
using System.Threading;
using System.Threading.Tasks;

namespace TPLExample3
{
    class Program
    {
        // create cancellation token source
        private static CancellationTokenSource cts = new CancellationTokenSource();
        // define cancellation token
        private static CancellationToken ct;

        static void Main(string[] args)
        {
            //assign the token from source to token object
            ct = cts.Token;
            try
            {
                // create and start task1
                Task task1 = new Task(TaskMethod1, ct);
                Console.WriteLine("starting the task1");
                task1.Start();

                // create and start task2
                Task task2 = new Task(TaskMethod1, ct);
                Console.WriteLine("starting the task2");
                task2.Start();

                // comment this code or reduce the value to < 1 sec to view the result 2.
                Thread.Sleep(1100);

                Console.WriteLine("cancelling the tasks using one token source.");
                cts.Cancel();

                try
                {
                    Task.WaitAll(task1, task2);
                    // if both the tasks completes then show the results
                    Console.WriteLine("Task1 result: " + task1.Result.ToString() + " task2 result: " + task2.Result.ToString());
                }
                catch
                {
                    throw;
                }
            }
            catch (Exception E)
            {
                Console.Write("Error: " + E.Message);
            }
            Console.ReadLine();
        }

        private static int TaskMethod1()
        {
            ct.ThrowIfCancellationRequested();
            // simulate some work here...
            Console.WriteLine("Task start here... Thread ID: " + Thread.CurrentThread.ManagedThreadId.ToString());
            Thread.Sleep(1000);
            ct.ThrowIfCancellationRequested();
            return 200 / 100;
        }
    }
}
Result 1 Result1 Result 2 Result2 We have to analyze the results of the above program to learn more about the tasks. In the above program (code 2), the main thread initializes two tasks one after another. We also see that two more class members known as CancellationTokenSource and CancellationToken objects. The CancellationTokenSource class is the used to control the Task cancellation. Assume that your application is traversing the file server having several thousands of files for the content matching a keyword. This is a time taking process by all standards unless it is indexed!! The user or some other component of the application wants to stop this process. Now these two classes come in handy. Typically more than one task can be working on the file searching task. We can cancel all the tasks which are working on the file search using only one call to Cancel() method of the object of CancellationTokenSource. The task is configured to throw an exception when it is cancelled. This is achieved by calling the method ThrowIfCancellationRequested() of the CancellationToken within the task body. Surprisingly the exception thrown is not noticed by the calling thread until it calls the <Taskobject>.Wait() or Task.WaitAll() or Task.WaitAny() methods.  In the above code (code 2) the program does not show the results until all the tasks are completed successfully. Code 3
static void Main(string[] args)
    {
        // main thread
        // start a parent thread
        var parent1 = Task.Factory.StartNew(() =>
        {
            // start child 1
            var child1 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("starting Child1...");
                Thread.SpinWait(Int32.MaxValue / 2);
                Console.WriteLine("Child1 completed");
            }, TaskCreationOptions.AttachedToParent);

            // start child 2
            var child2 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("starting Child2...");
                Thread.SpinWait(Int32.MaxValue / 2);
                Console.WriteLine("child2 completed");
            }, TaskCreationOptions.AttachedToParent);

            Thread.Sleep(200);
            Console.WriteLine("parent1 completed");
        });

        // main thread
        Console.WriteLine("waiting for parent1... status: " + parent1.Status.ToString());
        Task.WaitAll(parent1);
        Console.WriteLine("Parent1 completed...status: " + parent1.Status.ToString());

        Console.WriteLine("hit any key to exit app");
        Console.ReadLine();
    }
Result 3 Result3 Result 4 In the above example (code 3) the concept of task creation options are used to demonstrate the synchronization aspects of the tasks. The Tasks can be created with certain options that specify the type of the task. This is available in the enum TaskCreationOptions. There are four options available namely “None”, “PreferFairness”, “LongRunning” and “AttachedToParent” in Dot Net v 4.0. The explanations of these are available in the Microsoft MSDN site   http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcreationoptions(v=vs.100).aspx The most important of these is the “AttachedToParent” option. When a child task is created using this option the parent task is bound to the child task. This means that until all the child tasks having initiated using this option are completed the parent task cannot the state. This does not mean that parent task will be Wait state, this just means that Parent task will not change the state to other states (for e.g. RanToCompletion). When the results 3 and 4 are compared one must not conclude that the parent1 have been blocked. This is explained in the example below (code 4 and result 5) which is self evident. The “AttachedToParent” works only at the first level and does not work further. Code 4
static void Main(string[] args)
    {
        // main thread
        // start a parent thread
        var parent1 = Task.Factory.StartNew(() =>
        {
            // start child 1
            var child1 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("starting Child1...");
                Thread.SpinWait(Int32.MaxValue / 2);
                Console.WriteLine("Child1 completed");
            }, TaskCreationOptions.AttachedToParent);

            // start child 2
            var child2 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("starting Child2...");

                var child211 = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("starting Child211...");
                    Thread.SpinWait(Int32.MaxValue / 2);
                    Console.WriteLine("Child211 completed");
                }, TaskCreationOptions.AttachedToParent);

                Thread.Sleep(100);
                Console.WriteLine("child2 completed");
            });

            Thread.Sleep(200);
            Console.WriteLine("parent1 completed");
        });
        // main thread
        Console.WriteLine("waiting for parent1... status: " + parent1.Status.ToString());
        Task.WaitAll(parent1);
        Console.WriteLine("Parent1 completed...status: " + parent1.Status.ToString());

        Console.WriteLine("hit any key to exit app");
        Console.ReadLine();
    }
Result 5 Result5

No comments:

Post a Comment