Introduction to Threads

Basics and Introduction of Multithreading and Threads


Category: Multithreading Tags: C#


Introduction

        Thread is a small set of instructions can be executed like separate lightweight process but actually these are not processes. A process is independent and can have multiple threads, threads are used to achieve parallelism and can work isolated from main execution stream or other threads.
        In C# we use System.Threading.Thread Class to create threads, in next section we will cover some basics like how to create threads, what are states of a thread, priority threads, how to use lock mechanism, what is a deadlock etc.

Implementation

        First, we will see how to create a thread, parameterized and non-parameterized threads. To implement this, we will create a console application, see code below which defines a method:

static void PrintNumbersDelay()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(i);
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }
}

Start a Thread: Above we created a method printing numbers 0 to 9 and we are using Thread.Sleep to make a delay of 1 second. And we have some ways to create thread from Main method like:

Thread t = new Thread(PrintNumbersDelay);
t.Start();

OR

Thread t = new Thread(() => PrintNumbersDelay()); // using lambda
t.Start();

OR

delegate void Fun();//using delegate, declare it outside of main method
Fun fun = PrintNumbersDelay; Thread t = new Thread(()=>fun()); t.Start();

If we have to pass some parameters like we have method:

static void PrintNumbers(int count)
{
    for (int i = 0; i < count; i++)
    {
        Console.WriteLine(i);
    }
}

Then we can execute threads with lambda or delegate:

Thread t = new Thread(() => PrintNumbers(5));
t.Start();

OR

delegate void Fun(int x);     //using delegate, declare it outside of main method
Fun fun = PrintNumbers; Thread t = new Thread(() => fun(5)); t.Start();

Thread Join: using Join method we can make control of program to wait until a thread finishes its job like:

Thread t = new Thread(()=>PrintNumbersDelay());
t.Start();
t.Join();
Console.WriteLine("Thread has been finished it’s job");

Above Console.WriteLine statement will not be executed until thread t finishes its job because of Join.
Abort a Thread: We can abort a thread anytime calling Abort method like:

Thread t = new Thread(() => PrintNumbersDelay());
t.Start();
Thread.Sleep(2000);
t.Abort();

above thread will run for 2 seconds then it will be aborted.
Thread State: We can determine a thread’s state using property ThreadState like:

Thread t = new Thread(() => PrintNumbersDelay());
t.Start();
for (int i = 0; i < 10; i++)
{
    Console.WriteLine(t.ThreadState);
    Thread.Sleep(1000);
}

Once we start thread I will print 0 to 4 in 1 second intervals. And for loop will be printing its state parallelly like “Running”, “WaitSleepJoin”, “Stopped”. Below are thread states in which thread can be present:

  1. Unstarted
  2. Running
  3. WaitSleepJoin
  4. SuspendRequested
  5. Suspended
  6. AbortRequested
  7. Aborted
  8. Stopped

Thread Priority: We can assign priority to a thread so the time slot assigned by CPU to a thread with highest priority will be bigger than a thread with low priority.

Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
Thread t1 = new Thread(()=>PrintNumbers(100));
Thread t2 = new Thread(() => PrintNumbers(100));
t1.Priority = ThreadPriority.Highest;
t1.Priority = ThreadPriority.Lowest;
t1.Start();
t2.Start();

If you run above program you could observe that t1 assigned big slots so when we see outputs t1 might print 0 to 30 but t2 with low priority might print only 0 to 5 and then again t1 might start from 31 and so on. First line of code is telling CPU to use only one core so we could see expected result. If CPU uses 2 cores for 2 threads, then both threads might get same time slots irrespective of their priority. C# has one enum called ThreadPriority which defines priories we have 5 priorities options:

  1. AboveNormal
  2. BelowNormal
  3. Normal
  4. Highest
  5. Lowest

Foreground and Background Threads: Whichever threads we create in C# are foreground threads means main program will be alive until all threads are alive, for making a thread background we need to set property IsBackground to true, background threads will be terminated once main program finishes execution and will not wait other threads to be completed.

Func<int, int> fun = (i) => { int j; for (j = 0; j < i; j++) { Console.WriteLine(Thread.CurrentThread.Name + " counted " + j); Thread.Sleep(1000); } return j; };
Thread t1 = new Thread(() => fun(5));  //foreground thread
t1.Name = "first";
Thread t2 = new Thread(() => fun(10));
t2.IsBackground = true;     //background thread
t2.Name = "second";
t1.Start();
t2.Start();

Above code we created one anonymous method calling by two threads, foreground thread is having 5 iterations and background thread is having 10 iterations, once we run program t1 and t2 executes 5 iterations then main thread complete its execution and terminates t2. t2 will not be able to run 10 iterations.
Locking with keyword lock: when we have multiple threads performing operations on an object, these operations might get overlapped at same time and might read or write wrong data (Which is called race condition). To synchronize, we use locks if one thread is performing operation on object will be locked for other threads and those will wait until object gets unlocked.

class CounterLocked
{
    public int Count { get; set; }
    private object obj = new object();
    public void Increment()
    {
        lock (obj)
        {
            Count++;
        }
    }
public void Decrement() { lock (obj) { Count--; } } }

Above we can see we are locking the statement where increment and decrement is being performed. We cannot use a value type in lock so we had to create object of object class.
Deadlock Condition: deadlock condition occurs when one thread locked an object1 waiting for an object2 to get unlocked which is locked by thread two and thread two which locked object2 and waiting for object1 to get unlocked which being locked by thread one. In this situation both threads wait for resources being locked by other one. To understand we will create one method:

class Sample
{
    public void DoLock(object obj1, object obj2)
    {
        lock (obj1)
        {
            Thread.Sleep(1000);
            lock (obj2);
        }
    }
}

And in main thread:

Deadlock.Sample sample = new Deadlock.Sample();
object obj1 = new object();
object obj2 = new object();
Thread t1 = new Thread(() => sample.DoLock(obj1, obj2));
t1.Start();
lock (obj2)
{
    Thread.Sleep(1000);
    if (Monitor.TryEnter(obj1, 3000))
    {
        Console.WriteLine("done");
    }
    else
Console.WriteLine("resource isn't available"); }

In above code, we can see once t1 starts immediately main thread locks obj2 and wait for 1 second meanwhile t1 started execution and locked obj1 and waits for 1 second. Now obj1 and obj2 are locked, t1 needs obj2 which is locked by main thread and main thread needs obj1 which is locked by t1. Here both will be waiting for resources which are locked by other one. Here we used Monitor.TryEnter which will return true or false after waiting 3 seconds, if can’t access obj1 it will print “resource isn't available”. We can use this construct to handle deadlock situation.


Like 0 People
Last modified on 1 September 2018
Nikhil Joshi

Nikhil Joshi
Ceo & Founder at Dotnetlovers
Atricles: 132
Questions: 9
Given Best Solutions: 9 *

Comments:

No Comments Yet

You are not loggedin, please login or signup to add comments:

Existing User

Login via:

New User



x