0% found this document useful (0 votes)
0 views

Threads

The document explains the Join method of the Thread class in C#, which allows the main thread to wait for child threads to complete their execution. It provides examples of using the Join method, including its overloaded versions that allow for waiting with or without a timeout. Additionally, it discusses thread synchronization and how to prevent data inconsistency when multiple threads access shared resources simultaneously.

Uploaded by

rupams2024
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
0 views

Threads

The document explains the Join method of the Thread class in C#, which allows the main thread to wait for child threads to complete their execution. It provides examples of using the Join method, including its overloaded versions that allow for waiting with or without a timeout. Additionally, it discusses thread synchronization and how to prevent data inconsistency when multiple threads access shared resources simultaneously.

Uploaded by

rupams2024
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 20

Join Method of Thread Class in C#.

Let us understand the use of the Join Method of Thread Class in C# with Examples.
For a better understanding, please have a look at the following example. In the below
example we have created three methods and then execute these three methods
using three different threads. The point that you need to remember is the threads
thread1, thread2, and thread3 are called the child threads of the main thread. This is
because these three threads are created by the main thread only. Here, thread1
executes Method1, thread2 executes Method2, and thread3 executes Method3.
Further,

if you notice,

we have delayed the execution of Method1 by 3 seconds, Method2 execution by 2


seconds, and Method3 execution by 5 seconds

using the Thread class static Sleep method which takes the time in milliseconds and
will make the current thread sleep for that millisecond.

Here, we are using the overloaded version of the Thread class constructor which
takes the ThreadStart delegate as a parameter.

using System.Threading;
using System;
namespace ThreadingDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main Thread Started");
//Main Thread creating three child threads
Thread thread1 = new Thread(Method1);
Thread thread2 = new Thread(Method2);
Thread thread3 = new Thread(Method3);
thread1.Start();
thread2.Start();
thread3.Start();
Console.WriteLine("Main Thread Ended");
Console.Read();
}
static void Method1()
{
Console.WriteLine("Method1 - Thread1 Started");
Thread.Sleep(3000);

1
Console.WriteLine("Method1 - Thread 1 Ended");
}
static void Method2()
{
Console.WriteLine("Method2 - Thread2 Started");
Thread.Sleep(2000);
Console.WriteLine("Method2 - Thread2 Ended");
}
static void Method3()
{
Console.WriteLine("Method3 - Thread3 Started");
Thread.Sleep(5000);
Console.WriteLine("Method3 - Thread3 Ended");
}
}
}
Now, run the application and see the output. The output may vary on your machine
when you run the application.

As you can see from the above output, the Main thread is not waiting for all the child
threads to complete their execution or task.

If you want the Main thread should not be exited until and unless all the child thread
or any of the child threads completes their task then you need to use the Join method
of the Thread class in C#.

Join Method of Thread Class in C#:

The Join Method of the Thread Class in C# blocks the current thread and makes it
wait until the child thread on which the Join method invoked completes its execution.
There are three overloaded versions available for the Join Method in Thread class as
shown below.

2
The following are the definitions of the above three overloaded versions of the Join
Method of Thread class in C# provided by Microsoft:

1. Join(): This method blocks the calling thread until the thread represented by
this instance terminates while continuing to perform
standard COM and SendMessage pumping.

It will throw ThreadStateException if the caller attempted to join a thread that


is in the System.Threading.ThreadState.Unstarted state.

2. Join(int millisecondsTimeout): This method blocks the calling thread until


the thread represented by this instance terminates or the specified time
elapses while continuing to perform standard COM and SendMessage
pumping. The parameter millisecondsTimeout specifies the number of
milliseconds to wait for the thread to terminate. It returns true if the thread has
terminated; false if the thread has not terminated after the amount of time
specified by the millisecondsTimeout parameter has elapsed. It will throw
ArgumentOutOfRangeException if the value of millisecondsTimeout is
negative and is not equal to System.Threading.Timeout.Infinite in milliseconds.
It will throw ThreadStateException if the thread has not been started.

3. Join(TimeSpan timeout): This method blocks the calling thread until the
thread represented by this instance terminates or the specified time elapses
while continuing to perform standard COM and SendMessage pumping. Here,
the parameter timeout specifies a System.TimeSpan is set to the amount of
time to wait for the thread to terminate. It returns true if the thread is
terminated; false if the thread has not terminated after the amount of time
specified by the timeout parameter has elapsed.

It throws ArgumentOutOfRangeException if the value of timeout is negative and is


not equal to System.Threading.Timeout.Infinite in milliseconds, or greater than
System.Int32.MaxValue milliseconds.
It throws ThreadStateException if the caller attempted to join a thread that is in the
System.Threading.ThreadState.Unstarted state.
We can simplify the above definitions as follows:

Join(): The first version of the Join method which does not take any parameter will
block the calling thread (i.e. the Parent thread) until the thread (child thread)
completes its execution. In this case, the calling thread is going to wait for an
indefinite time until the thread on which the Join Method is invoked is completed.

Join(int millisecondsTimeout): The second version of the Join Method allows us to


specify the time out. It means it will block the calling thread until the child thread

3
terminates or the specified time elapses. This overloaded takes time in milliseconds.
This method returns true if the thread has terminated and returns false if the thread
has not terminated after the amount of time specified by the millisecondsTimeout
parameter has elapsed.

Join(TimeSpan timeout): The third overloaded version of this method is the same
as the second overloaded version. The only difference is that here we need to use
TimeSpan to set the amount of time to wait for the thread to terminate.

Example to Understand the Join Method of Thread Class in C#

For a better understanding of how to use the Thread Class Join Method in C#, please
have a look at the below example. In the below example, we have called the Join
method on all three threads which means it will block the main thread until all the
child threads complete their tasks.

Advertisements
using System.Threading;
using System;
namespace ThreadingDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main Thread Started");
//Main Thread creating three child threads
Thread thread1 = new Thread(Method1);
Thread thread2 = new Thread(Method2);
Thread thread3 = new Thread(Method3);
thread1.Start();
thread2.Start();
thread3.Start();
thread1.Join(); //Block Main Thread until
thread1 completes its execution
thread2.Join(); //Block Main Thread until
thread2 completes its execution
thread3.Join(); //Block Main Thread until
thread3 completes its execution
Console.WriteLine("Main Thread Ended");
Console.Read();
}
static void Method1()
{

4
Console.WriteLine("Method1 - Thread1 Started");
Thread.Sleep(1000);
Console.WriteLine("Method1 - Thread 1 Ended");
}
static void Method2()
{
Console.WriteLine("Method2 - Thread2 Started");
Thread.Sleep(2000);
Console.WriteLine("Method2 - Thread2 Ended");
}
static void Method3()
{
Console.WriteLine("Method3 - Thread3 Started");
Thread.Sleep(5000);
Console.WriteLine("Method3 - Thread3 Ended");
}
}
}
Now, run the above code and you will see that the Main thread is started first
and will wait until all the child threads complete their execution as shown in
the below output.

Now, for example, if you don’t want the main thread to wait until thread3 completes
its execution. Then you just need to call the Join method on thread1 and thread2 as
shown in the below example.

using System.Threading;
using System;
namespace ThreadingDemo
{
class Program
{
static void Main(string[] args)
{

5
Console.WriteLine("Main Thread Started");
//Main Thread creating three child threads
Thread thread1 = new Thread(Method1);
Thread thread2 = new Thread(Method2);
Thread thread3 = new Thread(Method3);
thread1.Start();
thread2.Start();
thread3.Start();
thread1.Join(); //Block Main Thread until
thread1 completes its execution
thread2.Join(); //Block Main Thread until
thread2 completes its execution
//Now, Main Thread will not wait for thread3 to
complete its execution
Console.WriteLine("Main Thread Ended");
Console.Read();
}
static void Method1()
{
Console.WriteLine("Method1 - Thread1 Started");
Thread.Sleep(1000);
Console.WriteLine("Method1 - Thread 1 Ended");
}
static void Method2()
{
Console.WriteLine("Method2 - Thread2 Started");
Thread.Sleep(2000);
Console.WriteLine("Method2 - Thread2 Ended");
}
static void Method3()
{
Console.WriteLine("Method3 - Thread3 Started");
Thread.Sleep(5000);
Console.WriteLine("Method3 - Thread3 Ended");
}
}
}
Output:

6
Other Overloaded Versions of Thread Class Join Method in C#:

You need to use the second and third overloaded version of the Thread Class Join
Method in C# when you want the main thread to wait for a specified amount of time.
For example, you want the main thread to wait for 3 seconds for thread2 and thread3
to complete their task. Then you need to use the Join method as shown below in the
below example. Remember the overloaded versions which take time in milliseconds
and TimeSpan returns a boolean value indicating whether the thread completes its
execution or not. Boolean true means method execution completed and false means
method execution not completed.

using System.Threading;
using System;
namespace ThreadingDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main Thread Started");
//Main Thread creating three child threads
Thread thread1 = new Thread(Method1);
Thread thread2 = new Thread(Method2);
Thread thread3 = new Thread(Method3);
thread1.Start();
thread2.Start();
thread3.Start();
//Now, Main Thread will block for 3 seconds and
wait thread2 to complete its execution
if (thread2.Join(TimeSpan.FromSeconds(3)))
{
Console.WriteLine("Thread 2 Execution Completed
in 3 second");
}
else

7
{
Console.WriteLine("Thread 2 Execution Not
Completed in 3 second");
}
//Now, Main Thread will block for 3 seconds and
wait thread3 to complete its execution
if (thread3.Join(3000))
{
Console.WriteLine("Thread 3 Execution Completed
in 3 second");
}
else
{
Console.WriteLine("Thread 3 Execution Not
Completed in 3 second");
}
Console.WriteLine("Main Thread Ended");
Console.Read();
}
static void Method1()
{
Console.WriteLine("Method1 - Thread1 Started");
Thread.Sleep(1000);
Console.WriteLine("Method1 - Thread 1 Ended");
}
static void Method2()
{
Console.WriteLine("Method2 - Thread2 Started");
Thread.Sleep(2000);
Console.WriteLine("Method2 - Thread2 Ended");
}
static void Method3()
{
Console.WriteLine("Method3 - Thread3 Started");
Thread.Sleep(5000);
Console.WriteLine("Method3 - Thread3 Ended");
}
}
}
Now, run the application and observe the output. You will see that Method2
execution is completed in 3 seconds while Method3 execution is not completed.
Further notice after the Main method execution, Method3 is going to complete its

8
execution. So, the Main thread is blocked for 3 seconds and after 3 seconds the Main
thread continues its execution.

IsAlive Property of Thread Class in C#:

The IsAlive property gets a value indicating the execution status of the current
thread. It returns true if the thread has been started and has not terminated normally
or aborted; otherwise, false. That means the IsAlive property of the Thread class
returns true if the thread is still executing else returns false. Let us understand this
with an example.

using System.Threading;
using System;
namespace ThreadingDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main Thread Started");
Thread thread1 = new Thread(Method1);
thread1.Start();
if (thread1.IsAlive)
{
Console.WriteLine("Thread1 Method1 is still
Executing");
}
else
{
Console.WriteLine("Thread1 Method1 Completed
its work");
}
//Wait Till thread1 to complete its execution

9
thread1.Join();
if (thread1.IsAlive)
{
Console.WriteLine("Thread1 Method1 is still
Executing");
}
else
{
Console.WriteLine("Thread1 Method1 Completed
its work");
}
Console.WriteLine("Main Thread Ended");
Console.Read();
}
static void Method1()
{
Console.WriteLine("Method1 - Thread1 Started");
//Making thread to sleep for 2 seconds
Thread.Sleep(TimeSpan.FromSeconds(2));
Console.WriteLine("Method1 - Thread 1 Ended");
}
}
}
Output:

What is Thread Synchronization in C#?

Data inconsistency occurs when more than one threads access a shared resource
such as in-memory data (instance or class variables) and external objects such as
files at the same time.

Let us understand this with an example. Consider that we have two threads

Thread1 and Thread2, and both the threads access a shared resource

10
let’s say Resource1 simultaneously. If Thread1 is trying to read data from the shared
resource Resource1 when Thread2 is attempting to write data onto the shared
resource Resource1, then there would be data inconsistency. Hence, in situations
like this thread synchronization comes into the picture.

Synchronization in C# language is a process that allows access to shared resources


smoothly. Synchronization in C# ensures that only one thread is accessing the
shared resource at any given point in time, preventing other threads from doing the
same at the same time.

Thread Synchronization in C# is a mechanism that is used to restrict multiple threads


from accessing a shared resource at the same time.

In simple words, we can also say that thread synchronization can help us to prevent
multiple threads from gaining access to a shared resource simultaneously

. As a result, we can have one and only one thread entering a critical section to
access the shared resource at any given point in time.

How Thread Synchronization is Achieved in C#?

Synchronization in C# can be achieved in multiple ways.

One of the ways to achieve Synchronization in C# is by using the feature of lock,


which locks the access to a block of code within the locked object.

When a thread locks an object, no other thread can access the block of code within
the locked object. Only when a thread releases the lock, then it is available for other
threads to access it.

In C# Language, every object has a built-in lock. By using the feature of


Synchronization, we can lock an object.

Locking an object can be done by using the lock keyword, and the following is the
syntax to use the lock.

lock(object)
{
//Statement1
//Statement2
//And more statements to be synchronized
}

So, when a thread acquires a lock over an object, then that particular thread can only
access the block of statements within the locked object.

11
Now, all the other threads wishing to access the same block of statements within the
same locked object will have to wait until, the thread that has got the lock on the
object, releases the lock, by exiting the block of statements.

Example without Thread Synchronization in C#:

Before we show you the example of how to use the synchronization between threads
by locking an object and its practical use,

let us first see what actually happens without using synchronization on executing
multiple threads, which are trying to access the same resource.

In the below example, we are creating three different threads that are going to going
to access the same resource i.e. in this case the shared resource is SomeMethod.
As you can see, we have delayed the SomeMethod execution by 1 second, and all
three threads trying to execute the same method and they will execute it.

The first thread which executes the method does not get its sole access, this thread
executes the method for a while and after some time, other threads will come and
execute the same method. So, here, we will not get the output as expected.

using System;
using System.Threading;
namespace ThreadStateDemo
{
class Program
{
static void Main(string[] args)
{
Thread thread1 = new Thread(SomeMethod)
{
Name = "Thread 1"
};
Thread thread2 = new Thread(SomeMethod)
{
Name = "Thread 2"
};
Thread thread3 = new Thread(SomeMethod)
{
Name = "Thread 2"
};
thread1.Start();
thread2.Start();
thread3.Start();
Console.ReadKey();

12
}
public static void SomeMethod()
{
Console.Write("[Welcome To The ");
Thread.Sleep(1000);
Console.WriteLine("World of Dotnet!]");
}
}
}
When I run the above code, I am getting the following output. The output might be
varied in your machine.

As you can see in the above output, here we are not getting the output as expected.

So, the point that you need to keep in mind is that if the shared resource is not
protected in a multithreaded environment from concurrent access, then the output or
the behavior of the application becomes inconsistent.

Example using Thread Synchronization in C#

In the below example, we are creating three threads that are going to access the
SomeMethod, but this time access to SomeMethod will be synchronized because we
are going to use the lock, to lock the object within which the method is going to be
accessed by multiple threads. The first thread to enter the method gets its sole
access until it exits the method, thereby avoiding the collision between multiple
threads trying to access a method.

using System;
using System.Threading;
namespace ThreadStateDemo
{
class Program
{
static object lockObject = new object();
static void Main(string[] args)
{
Thread thread1 = new Thread(SomeMethod)
{
Name = "Thread 1"
};
Thread thread2 = new Thread(SomeMethod)

13
{
Name = "Thread 2"
};
Thread thread3 = new Thread(SomeMethod)
{
Name = "Thread 2"
};
thread1.Start();
thread2.Start();
thread3.Start();
Console.ReadKey();
}
public static void SomeMethod()
{
// Locking the Shared Resource for Thread
Synchronization
lock (lockObject)
{
Console.Write("[Welcome To The ");
Thread.Sleep(1000);
Console.WriteLine("World of Dotnet!]");
}
}
}
}
Output:

Advertisements

The first thread enters the SomeMethod locked the method (code written within the
lock (object)), and gets its sole access and once this thread has finished its execution
of the method,

then only another thread will come and acquire a lock on the critical section i.e. the
code written within the lock object.

In this way, the lock object will make sure that at any given point in time, only one
thread can access the shared resource which will also ensure data consistency.

14
Realtime Example to Understand Thread Synchronization in C#:

Let us see one real-time example to understand thread synchronization and why it is
important to protect shared resources in our application. In the below example, I am
going to show you how to protect a shared variable from concurrent access in a
multithread environment.

In the below example, we are implementing the ticket booking functionality for a
movie. Let us assume the number of available tickets is 3 and three different threads
tried to book tickets. Thread1 tries to book 1 ticket, thread2 tries to book 2 tickets and
thread3 tries to book 3 tickets. Let us first see the problem without thread
synchronization. In the below code, we are not implementing thread synchronization
and hence sometime you will see that all three threads are able to book tickets. See,
the available tickets are 3 and we are able to book 6 tickets and this is the issue
without thread synchronization.

using System;
using System.Threading;
namespace ThreadStateDemo
{
class Program
{
static void Main(string[] args)
{
BookMyShow bookMyShow = new BookMyShow();
Thread t1 = new Thread(bookMyShow.TicketBookig)
{
Name = "Thread1"
};
Thread t2 = new Thread(bookMyShow.TicketBookig)
{
Name = "Thread2"
};
Thread t3 = new Thread(bookMyShow.TicketBookig)
{
Name = "Thread3"
};
t1.Start();
t2.Start();
t3.Start();
Console.ReadKey();
}
}
public class BookMyShow

15
{
int AvailableTickets = 3;
static int i = 1, j = 2, k = 3;
public void BookTicket(string name, int
wantedtickets)
{
if (wantedtickets <= AvailableTickets)
{
Console.WriteLine(wantedtickets + " booked to "
+ name);
AvailableTickets = AvailableTickets -
wantedtickets;
}
else
{
Console.WriteLine("No tickets Available to
book");
}
}
public void TicketBookig()
{
string name = Thread.CurrentThread.Name;
if (name.Equals("Thread1"))
{
BookTicket(name, i);
}
else if (name.Equals("Thread2"))
{
BookTicket(name, j);
}
else
{
BookTicket(name, k);
}
}
}
}
Output:

16
As you can see in the above output, all the threads are able to book the tickets. This
is possible because all the threads are able to access the critical section code of the
program simultaneously. Now, let us proceed and see how we can restrict this i.e.
how we can allow only one thread to execute the critical section code.

Real-Time Example using Thread Synchronization in C#

In the below example, we have used the thread synchronization mechanism to lock
the critical section code by using the lock object. Now, the lock object will make sure
that only one thread can execute the critical section code, and once the thread
completes the execution of the critical section code, then another thread can enter
and execute the critical section code. With this, now you will get the output as
expected and you will see that you cannot book more than 3 tickets.

using System;
using System.Threading;
namespace ThreadStateDemo
{
class Program
{
static void Main(string[] args)
{
BookMyShow bookMyShow = new BookMyShow();
Thread t1 = new Thread(bookMyShow.TicketBookig)
{
Name = "Thread1"
};
Thread t2 = new Thread(bookMyShow.TicketBookig)
{
Name = "Thread2"
};
Thread t3 = new Thread(bookMyShow.TicketBookig)
{
Name = "Thread3"
};
t1.Start();
t2.Start();
t3.Start();
Console.ReadKey();
}
}
public class BookMyShow
{

17
private object lockObject = new object();
int AvailableTickets = 3;
static int i = 1, j = 2, k = 3;
public void BookTicket(string name, int
wantedtickets)
{
lock(lockObject)
{
if (wantedtickets <= AvailableTickets)
{
Console.WriteLine(wantedtickets + " booked to "
+ name);
AvailableTickets = AvailableTickets -
wantedtickets;
}
else
{
Console.WriteLine("No tickets Available to
book");
}
}
}
public void TicketBookig()
{
string name = Thread.CurrentThread.Name;
if (name.Equals("Thread1"))
{
BookTicket(name, i);
}
else if (name.Equals("Thread2"))
{
BookTicket(name, j);
}
else
{
BookTicket(name, k);
}
}
}
}
Output:

18
So, by using the feature of synchronization (lock), we can avoid a conflict between
threads trying to access the same resource simultaneously. We can achieve thread
synchronization in C# by using the followings. From our next article onwards, we are
going to discuss the following in detail.

1. Lock
2. Monitor
3. Mutex
4. Semaphore
5. SemaphoreSlim

Note: Thread Synchronization in C# is a mechanism that ensures that two or more


concurrent processes or threads do not execute some particular section of the
program, especially the critical section. In this technique, one thread executes the
critical section of a program and the other thread waits until the thread finished its
execution. If a proper synchronization mechanism will be not applied then race
conditions will happen.

Why do we need Thread Synchronization in Multithreading?

We need Thread Synchronization in Multithreading because of the following:

1. Atomicity: Thread Synchronization supports atomicity, which ensures that


multiple threads in the application are not allowed to access a shared resource
concurrently to prevent data inconsistency. The code section of our program
which causes data inconsistency is known as the critical section. The critical
section of our program is executed atomically by one and only one thread
which ensures Atomicity. The Ticket booking example is an example of
Atomicity.
2. Ordering: We generally want two or more threads to perform a task in a
particular order or we want to restrict access to shared resources to a
particular number of threads only. Usually, we don’t have much control over all
this, which is one reason for race conditions. Thread synchronization provides
support for ordering so that you can have control over your threads to perform
the tasks as per your requirement. The first example that we discussed in this
article is the example of ordering.

What is Exclusive Lock and Non-Exclusive Lock in C#?

When a process or a thread wants to access an object, it requests a lock on that


object. There are two types of locks that determine access to shared resources –
Exclusive Lock and Non-Exclusive lock.

1. Exclusive Lock: An exclusive lock makes sure that only one thread can gain
access or enter a critical section at any given point in time. In C#, we can

19
implement Exclusive Lock using the lock keyword, Monitor class, Mutex Class,
and SpinLock class.
2. Non-Exclusive Lock: Non-Exclusive locks provide read-only access to a
shared resource and limit concurrency, i.e., limit the number of concurrent
accesses to a shared resource. In C#, we can implement Non-Exclusive Lock
using the Semaphore, SemaphoreSlim, and ReaderWriterLockSlim classes.

20

You might also like