Click here to Skip to main content
15,879,535 members
Articles / Programming Languages / C#
Article

Threads and Thread Synchronization in C#

Rate me:
Please Sign up or sign in to vote.
4.29/5 (37 votes)
16 May 2008CPOL9 min read 269.6K   65   24
An article that discusses the advantages of C# multithreaded programming

Introduction

Although C# contains many innovative features, one of the most exciting is its built in support for multithreaded programming.

A multithreaded program contains two or more parts that can run concurrently. Each part of such a program is called a thread, and each thread defines a separate path of execution. Thus, multithreading is a specialized form of multitasking.

Multithreaded applications provide the illusion that numerous activities are happening at more or less the same time. But the reality is the CPU uses something known as “Time Slice” to switch between different threads.

The principal advantage of multithreading is that it enables you to write very efficient programs because it lets you utilize the idle time that is present in most programs. But using too many threads in our programs can actually “degrade” performance, as the CPU must switch between the active threads in the process which takes time. When a thread’s time slice is up, the existing thread is suspended to allow other thread to perform its business. For a thread to remember what was happening before it was kicked out of the way, it writes information to its local storage and it is also provided with a separate call stack, which again put extra load on the CPU.

- from Herbert Schildt's book "C# 2.0 The complete Reference". chepter 22

Multithreading Fundamentals

There are two distinct types of multitasking: process-based and thread-based.

The differences between process-based and thread-based multitasking can be summarized like this:

Process-based multitasking handles the concurrent execution of programs, while Thread-based multitasking deals with the concurrent execution of pieces of the same program.

Process-based: Example — running word processor at the same time you are browsing the net.
Thread-based: Example — A text editor can be formatting text at the same time that it is printing.

Simply we can define a thread as a line of execution within a process and it can exist in any of these several states.

It can be running. It can be ready to run as soon as it gets CPU time. A running thread can be suspended, which is a temporary halt to its execution. It can later be resumed, A thread can be blocked when waiting for a resource. A thread can be terminated, in which case its execution ends and cannot be resumed.

The .NET Framework defines two types of threads: foreground and background.

By default when you create a thread, it is a foreground thread, but you can change it to a background thread. The only difference between a foreground and background thread is that a background thread will be automatically terminated when all foreground threads in its process have stopped.

The “Foreground” threads have the ability to prevent the current application from terminating. The CLR will not shutdown an application until all foreground threads have ended. The “Background” threads are viewed by the CLR as expandable paths of execution that can be ignored at any point of time even if they are laboring over some unit of work. Thus, if all foreground threads have terminated, any background threads operating are automatically killed when the application terminates.

All processes have at least one thread of execution, which is usually called the main thread because it is the one that is executed when your program begins. From the main thread you can create other threads.

The classes that support multithreaded programming are defined in the System.Threading namespace.

Thus, you will usually include this statement at the start of any multithreaded program:

C#
using System.Threading;

Creating a Thread

To create a thread, you instantiate an object of type Thread. Thread defines the following constructor:

C#
public Thread( ThreadStart entrypoint)

Here, entrypoint is the name of the method that will be called to begin execution of the thread. ThreadStart is a delegate defined by the .NET Framework as shown here:

C#
public delegate void ThreadStart()

Thus, your entrypoint method must have a void return type and take no arguments.

Once created, the new thread will not start running until you call its Start() method, which is defined by Thread. The Start() method is shown here:

C#
public void Start()

Once started, the thread will run until the method specified by entryPoint returns. Thus, when entryPoint returns, the thread automatically stops. If you try to call Start() on a thread that has already been started, a ThreadStateException will be thrown.

Example

C#
using System;
using System.Threading;
namespace CSharpThreadExample
{
    class Program
    {
        public static void run()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("In thread " + Thread.CurrentThread.Name + i);
                Thread.Sleep(1000);
            }   
        }
        static void Main(string[] args)
        {
            Console.WriteLine("Main Thread Starting");
            Thread.CurrentThread.Name = "Main ";

            Thread t1 = new Thread(new ThreadStart(run));
            t1.Name = "Child";
            t1.Start();

            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("In thread " + Thread.CurrentThread.Name + i);
                Thread.Sleep(1000);
            }
            Console.WriteLine("Main Thread Terminates");
            Console.Read();
        }
    }
}

Notice the call to Sleep(), which is a static method defined by Thread.
The Sleep() method causes the thread from which it is called to suspend execution for the specified period of milliseconds. The form used by the program is shown here:

C#
public static void Sleep(int milliseconds)

The number of milliseconds to suspend is specified in milliseconds. If milliseconds is zero, the calling thread is suspended only to allow a waiting thread to execute.

Here’s the output:

image001.jpg

Each thread maintains a private set of structures that the O/S uses to save information(the thread context) when the thread is not running including the value of CPU registers. It also maintains the priority levels. We can assign higher priority to a thread of more important task than to a thread which works in background. In all cases time slice allocated to each thread is relatively short, so that the end user has the perception that all the threads (and all the applications) are running concurrently.

O/S has thread scheduler which schedules existing threads and preempts the running thread when its time slice expires. We make the most use of multithreading when we allocate distinct threads to tasks that have different priorities or that take a lot of time to complete.

The main problem with threads is that they compete for shared resource, a resource can be a variable, a database connection, a H/W device. We must synchronize the access to such resources — otherwise we will result in deadlock situations. A thread terminates when the task provided to it terminates or when the thread is programmatically killed by calling Abort(). An application as a whole terminates only when all its threads terminate.

Methods

  • Suspend() -> Suspends the execution of a thread till Resume() is called on that.
  • Resume() -> Resumes a suspended thread. Can throw exceptions for bad state of the thread.
  • Sleep() -> A thread can suspend itself by calling Sleep(). Takes parameter in form of milliseconds. We can use a special timeout 0 to terminate the current time slice and give other thread a chance to use CPU time
  • Join()-> Called on a thread makes other threads wait for it till it finishes its task.

States of a Thread

States of a thread can be checked using ThreadState enumerated property of the Thread object which contains a different value for different states.

  • Aborted -> Aborted already.
  • AbortRequested -> Responding to an Abort() request.
  • Background -> Running in background. Same as IsBackground property.
  • Running -> Running after another thread has called the start()
  • Stopped -> After finishing run() or Abort() stopped it.
  • Suspended -> Suspended after Suspend() is called.
  • Unstarted -> Created but start() has not been called.
  • WaitSleepJoin -> Sleep()/Wait() on itself and join() on another thread. If a thread Thread1 calls sleep() on itself and calls join() on the thread Thread2 then it enters WaitSleepJoin state. The thread exists in this state till the timeout expires or another thread invokes Interrupt() on it.

It is wise to check the state of a thread before calling methods on it to avoid ThreadStateException.

image002.gif

This picture describes in detail about the states of the thread [ Collected from “Thinking in C#” by Bruce Eckel ]

Properties of a Thread

  • Thread.CurrentThread -> Static method gives the reference of the thread object which is executing the current code.
  • Name -> Read/Write Property used to get and set the name of a thread
  • ThreadState -> Property used to check the state of a thread.
  • Priority -> Property used to check for the priority level of a thread.
  • IsAlive -> Returns a Boolean value stating whether the thread is alive or not.
  • IsBackground -> Returns a Boolean value stating the running in background or foreground.

PriorityLevels of Thread

Priority levels of thread is set or checked by using an enumeration i.e. ThreadPriority. The valid values are for this enumeration are;

  • Highest
  • AboveNormal
  • Normal
  • BelowNormal
  • Lowest

Synchronization in Threads

When we have multiple threads that share data, we need to provide synchronized access to the data. We have to deal with synchronization issues related to concurrent access to variables and objects accessible by multiple threads at the same time. This is controlled by giving one thread a chance to acquire a lock on the shared resource at a time. We can think it like a box where the object is available and only one thread can enter into and the other thread is waiting outside the box until the previous one comes out.

C#
using System;
using System.Threading;
namespace CSharpThreadExample

{
    class Program
    {
        static void Main(string[] arg)
        {
            Console.WriteLine("*****Multiple Threads*****");
            Printer p=new Printer();
            Thread[] Threads=new Thread[3];
            for(int i=0;i<3;i++)
            {
                Threads[i]=new Thread(new ThreadStart(p.PrintNumbers));
                Threads[i].Name="Child "+i;
            }
            foreach(Thread t in Threads)
                t.Start();

            Console.ReadLine();
        }
    }
    class Printer
    {
        public void PrintNumbers()
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(100);
                Console.Write(i + ",");
            }
            Console.WriteLine();
        }
    }
}

In the above example, we have created three threads in the main method and all the threads are trying to use the PrintNumbers() method of the same Printer object to print to the console. Here we get this type of output:

image003.jpg

Now we can see, as the thread scheduler is swapping threads in the background each thread is telling the Printer to print the numerical data. We are getting inconsistent output as the access of these threads to the Printer object is synchronized. There are various synchronization options which we can use in our programs to enable synchronization of the shared resource among multiple threads.

Using the Lock Keyword

In C# we use lock(object) to synchronize the shared object.

Syntax:

C#
lock (objecttobelocked)    {

    objecttobelocked.somemethod();
}

Here objecttobelocked is the object reference which is used by more than one thread to call the method on that object. The lock keyword requires us to specify a token (an object reference) that must be acquired by a thread to enter within the lock scope. When we are attempting to lock down an instance level method, we can simply pass the reference to that instance. (We can use this keyword to lock the current object) Once the thread enters into a lock scope, the lock token (object reference) is inaccessible by other threads until the lock is released or the lock scope has exited.

If we want to lock down the code in a static method, we need to provide the System.Type of the respective class.

Converting the Code to Enable Synchronization using the Lock Keyword

C#
public void PrintNumbers()
{
    lock (this)
    {
        for (int i = 0; i < 5; i++)
        {
            Thread.Sleep(100);
            Console.Write(i + ",");
        }
        Console.WriteLine();
    }
}

OUTPUT

image004.jpg

Using the Monitor Type

The C# lock keyword is just a notation for using System.Threading.Monitor class type. The lock scope actually resolves to the Monitor class after being processed by the C# compiler.

Converting the Code to Enable Synchronization using the Monitor Class

C#
public void PrintNumbers()
{
    Monitor.Enter(this);
        try
    {
        for (int i = 0; i < 5; i++)
        {
            Thread.Sleep(100);
            Console.Write(i + ",");
        }
        Console.WriteLine();
    }
    finally
    {
        Monitor.Exit(this);
    }
}

Monitor.Enter() method is the ultimate recipient of the thread token. We need to write all code of the lock scope inside a try block. The finally clause ensures that the thread token is released(using the Monitor.Exit() method), regardless of any runtime exception.

OUTPUT

image005.jpg

In this article I have tried to make the explanation as simple as possible. I have referred the book written by Andrew Troelson published by Apress. Please provide your valuable comments and suggestions for improvement of this article. I am always open for future upgradation of the article.

License

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



Comments and Discussions

 
GeneralMy vote of 4 Pin
Umesh AP14-Dec-15 19:37
Umesh AP14-Dec-15 19:37 
GeneralMy vote of 3 Pin
arunkumar812713-May-14 21:20
arunkumar812713-May-14 21:20 
QuestionGood. Pin
dka728-Apr-14 19:52
dka728-Apr-14 19:52 
GeneralSelf-Explanatory for beginners, Good One Pin
Vara Prasad K5-Dec-13 20:07
Vara Prasad K5-Dec-13 20:07 
GeneralVery useful Pin
Bhimrao A Kawle19-Nov-13 1:37
professionalBhimrao A Kawle19-Nov-13 1:37 
GeneralMy vote of 5 Pin
Nikolay Kim22-Jul-13 23:37
Nikolay Kim22-Jul-13 23:37 
GeneralMy vote of 5 Pin
Ashakoti18-Jun-13 18:09
Ashakoti18-Jun-13 18:09 
QuestionQuestion Pin
Member 990145822-Apr-13 20:10
Member 990145822-Apr-13 20:10 
BugThe term "synchronized" is used incorrectly! Pin
Scammed by KeyCAPTCHA21-Feb-13 7:50
Scammed by KeyCAPTCHA21-Feb-13 7:50 
SuggestionLocking on "this" is bad practice Pin
zuraw7-Feb-13 23:30
zuraw7-Feb-13 23:30 
QuestionMulti Threading in Multi Processor Pin
Eng. bipin 2-Oct-11 0:56
Eng. bipin 2-Oct-11 0:56 
GeneralMonitor.Enter inside Try block, or just use lock/SyncLock Pin
Tamus25-Sep-10 18:46
Tamus25-Sep-10 18:46 
GeneralMy vote of 1 Pin
Tamus25-Sep-10 18:30
Tamus25-Sep-10 18:30 
GeneralMy vote of 2 Pin
span23719-Apr-10 21:04
span23719-Apr-10 21:04 
GeneralMy vote of 1 Pin
Ozgur Ozcitak17-Sep-09 11:48
Ozgur Ozcitak17-Sep-09 11:48 
QuestionBlatant plagiarism? Pin
Ravi Bhavnani16-May-08 7:27
professionalRavi Bhavnani16-May-08 7:27 
AnswerRe: Blatant plagiarism? Pin
Guillaume Leparmentier16-May-08 7:40
Guillaume Leparmentier16-May-08 7:40 
Smile | :) seems to be an unfortunate copy/paste
GeneralRe: Blatant plagiarism? Pin
Ravi Bhavnani16-May-08 7:44
professionalRavi Bhavnani16-May-08 7:44 
GeneralRe: Blatant plagiarism? Pin
AshokPatra18-May-08 20:28
AshokPatra18-May-08 20:28 
GeneralRe: Blatant plagiarism? Pin
Ravi Bhavnani19-May-08 5:20
professionalRavi Bhavnani19-May-08 5:20 
GeneralRe: Blatant plagiarism? Pin
AshokPatra19-May-08 20:11
AshokPatra19-May-08 20:11 
GeneralRe: Blatant plagiarism? Pin
Tamus25-Sep-10 18:35
Tamus25-Sep-10 18:35 
GeneralEventWaitHandles Pin
r.chiodaroli16-May-08 7:04
r.chiodaroli16-May-08 7:04 
GeneralDon't forget Pin
PIEBALDconsult16-May-08 6:06
mvePIEBALDconsult16-May-08 6:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.