Threading and Task Processing Library in C#

Threading


Need of Thread

Let us think your computer have only on CPU or a single core processor capable of running one operation at  a time, if CPU has to process a long running task, till the time no other task would run until the current task completes, while this task run all other application will remain unresponsive which forces you to restart the system to fix the issue.

             Here thread came into existence. In current windows each application have it's own processes that execute in threads, so if an application crashes only process associated with that application will affect.
Context Switching:  Windows manage execution of threads to ensure their work properly, each thread is allowed to execute for a certain period of time, after this period the thread is paused and windows switches to other thread. This is called context switching. However windows has to manage the time between switching, so that it saves the state of current thread to process again in it's turn. When the time between switching become very small then user have illusion that each process are running in parallel, we can create multiple threads on multiple core machine in parallel, so that it utilizes the processor. This is called parallel processing will discuss about it later.

Thread class: Thread class exists in system.Threading namespace, this class enables us to create and manage new threads.
   Multiple threads cannot perform same operations i.e if one thread is using output stream till the time other thread will become pause for accessing output stream, explained in below example.

using System;
using System.Threading;

namespace ThreadDemo
{
    class Program
    {
        static void ThreadMethod() {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("ThreadMethod:{0}", i);
                Thread.Sleep(0);
            }
        }
        static void Main(string[] args)
        {
            Thread t = new Thread(new ThreadStart(ThreadMethod));
            t.Start();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("MainThread:{0}", i);
                Thread.Sleep(0);
            }
            t.Join();
         }
    }
}

Output:
// MainThread:0
// ThreadMethod:0
// MainThread:1
// ThreadMethod:1
// ThreadMethod:2
// ThreadMethod:3
// MainThread:2
// MainThread:3
// MainThread:4
// ThreadMethod:4
// ThreadMethod:5
// ThreadMethod:6
// ThreadMethod:7
// ThreadMethod:8
// ThreadMethod:9

In above example a new thread has been created by passing a delegate ThreadStart which holds the reference of ThreadMethod. Here only one console operation will  be perform in both  the thread, if one thread is using Console.WriteLine statement, other one will be paused. Thread.Join method call to wait to complete other thread.
Foreground and Background thread: Foreground thread keeps your application alive, after all foreground thread end,  and then application shut down and background threads ends, by default all the threads are foreground, we have to set property IsBackground to true in order to create a background thread, following example illustrate the use of both threads.

       static void ThreadT1()
       {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("T1:{0}", i);
                Thread.Sleep(1000);
            }
        }
        static void ThreadT2() {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("T2:{0}", i);
                Thread.Sleep(1000);
            }
        }
        static void Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(ThreadT1));
            t1.IsBackground = true;
            t1.Name = "t1";
            t1.Start();
            Thread t2 = new Thread(new ThreadStart(ThreadT2));
            t2.Name = "t2";
            t2.Start();
       }

Output:
//T1:0
//T2:0
//T1:1
//T2:1
//T1:2
//T2:2
//T1:3
//T2:3
//T1:4
//T2:4

As per the output above example, after completion of foreground thread T2 the background thread T1 will end immediately without completion of it's task, in other words program will not terminate until foreground thread T2 will not complete.

Overloads of Thread constructor:
Thread constructor have following overload.

a) new instance of ThreadStart delegate- used to pass threads without parameter.
Thread t=new Thread(new ThreadStart(ThreadMethod));

b) new instance of ParameterizedThreadStart delegate- used to pass parameter to thread

Thread t=new Thread(new ParameterizedThreadStart(ThreadMethod));
t.Start(2);       // 2 is a parameter passed to our thread method. 

c) new Instance of ThreadStart with parameters maxStackSize parameter as: 

   new Thread(new ThreadStart(ThreadMethod), int maxStackSize)


here maxStackSize is an integer variable represent maximum size of thread stack in bytes, however 0 is the default stack size if we not pass it.

d) new Instance of ParameterizedThreadStart delegate with maxStackSize parameter same as above.


Note:Beginning with the .NET Framework 4, only fully trusted code can set maxStackSize to a value that is greater than the default stack size (1 megabyte). If a larger value is specified for maxStackSize when code is running with partial trust, maxStackSize is ignored and the default stack size is used. No exception is thrown. Code at any trust level can set maxStackSize to a value that is less than the default stack size.
          If you are developing a fully trusted library that will be used by partially trusted code, and you need to start a thread that requires a large stack, you must assert full trust before creating the thread, or the default stack size will be used. Do not do this unless you fully control the code that runs on the thread.
  
Thread Pool:A created thread costs some work and time which cannot be reuse because it dies after finishes. Thread pool is created to reuse such threads, instead a thread to die you sends back to thread pool so that it can further reuse when a request come in.
    when working with thread pool we queue a work item that is then picked up by available threads from pool.

    class Thread
    {
        public static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(HandleUserRequest);
            ThreadPool.QueueUserWorkItem(ProcessUserRequest);
        }

        static void ProcessUserRequest(object obj) {
            Console.WriteLine("Use request processing");
        }
        static void HandleUserRequest(object obj)
        {
            Console.WriteLine("Handle user request");
        }

    }


Tasks

Task represent some work that should be done, it tell when the work will completed and if the operation returns a results, the Task gives the result.

    class TaskDemo
    {
        static void Main(string[] args)
        {
            Task t = Task.Run(() =>
              {
                  for (int i = 0; i < 100; i++)
                  {
                      Console.WriteLine("*");
                  }
              })
             t.Wait();
        }
    }

Here passing lambda expression inside Run method of task responsible of starting new task, Wait() method work same as join in thread to wait till the thread complete. 
Task<T> class is used to create a task that returns some result.

    class TaskDemo
    {
        static void Main(string[] args)
        {
            Task<string> t = Task<string>.Run(() =>
              {
                  for (int i = 0; i < 100; i++)
                  {
                      Console.WriteLine("*");
                  }
                  return "completed";
              });
            Console.WriteLine(t.Result);
        }
    }

Here program will not terminate until t.Result not have the result of task, if the task is not complete it will block the current thread.
Continuation task: we can continue a task by just chaining ContinueWith method after the task finishes.

        static void Main(string[] args)
        {
            Task<int> t = Task<int>.Run(() =>
              {
                  int _calc = 0;
                  for (int i = 0; i < 2; i++)
                  {
                      _calc = _calc + i;
                      Console.WriteLine("*");
                  }
                  return _calc;
              }).ContinueWith((calc) =>
              {
                  return calc.Result + 100;
              });
            Console.WriteLine(t.Result);
            Thread.Sleep(1000);
        }

//Output
//101

ContinueWith method takes value of _calc and return furture calculation to result.

Cancelling a Task: we can cancel a task whenever we want, as following:









Comments

Popular posts from this blog

Design Patterns