Table of contents
One fine Saturday evening I went for dinner with my family. Nothing unusual with the restaurant. Except I had the privilege of peeking into the kitchen, going behind closed doors for the first time, courtesy of a close friend who worked there. The head chef was the master calling all the shots. He had two workers chopping veggies for him, two doing the dishes, and five engaged on the cooking stove working on various dishes. Finally, at any given time a couple of workers, not actively working, were on standby to take his orders and execute on the word go.
The ingenuity of humans, I wondered!!
Is there an alternative way possible to run this restaurant?
Yes, the head chef is without a doubt adept at doing all the work single-handedly. I am sure he can deliver an exquisite dish given a chance. Except, by the time he comes serving the dish he may be disappointed to not find his customer who has long gone back home to satiate his hunger. There is no way you can run a restaurant to serve customers with minimal order wait time without having an army of workers working concurrently.
Well, applications are no different. Applications need to perform multiple sub-tasks to complete the parent task. It might entail tasks like I/O operation, database query execution and Http call etc. Modern programming languages have various means of supporting concurrency to serve the needs of applications. C# also has support for multithreading so that applications can perform more than one operation.
Concurrency is a broader term that encompasses multithreading, asynchronous operations, parallelism etc. The idea is to do multiple things concurrently to increase performance and throughput. This article discusses how we can use Thread
Class of C# to build multithreading programs.
Threads
A thread is a single sequential flow of control within a program. A thread doesn't run on its own. It is spawned and created by the program. The real essence of the concept of threads lies in the ability to run multiple threads concurrently and thus leading to multiple instances of programs running in parallel.
To understand the concept of multithreading we will illustrate it using a simple program.
As the adage goes, you can't appreciate light without darkness, so let's first look into how the code would look in a world without threading.
SomeTask
is a method that represents a task that takes 1 second to execute. We are using Thread.Sleep
(note this does not imply using threading)to simulate a task that would take 1 second.
SomeTask
is executed three times using a for loop. This is what happens when we execute the Main
function here.
Key points to note here are:
The three tasks get executed sequentially one after the other.
Each task gets finished before a new one starts.
The total execution time for 3 tasks is 3 seconds.
Now we will try to do the same work using Thread
class and we will try to have three executions of SomeTask
run concurrently.
First, let's create a thread.
This creates a new thread 't' by passing a method in Thread
class constructor. This is the method that the thread t will run on execution. Alternatively, we can use lambda expression to create a new thread if we need to pass parameters to the method.
Second, execute the thread by calling Start
on thread t.
Third, call Join
method to wait for the thread to get completed.
Now putting this all together to run the SomeTask
using threads.
This is what we get as output :
Key points to note here are:
Tasks 2 and 3 get started even before task 1 has been completed.
Task 2 starts after task 1 but gets finished before task 1.
The total execution time taken is 1 second.
The reason for the above execution is very interesting and key to understanding the threads and concurrency.
The Main
method itself is running on a thread. Let's call it the main thread. This main thread initiates three threads t1, t2, and t3. The main thread executes t1 first with the Start
method call. At this point, t1 starts executing SomeTask
. The main thread is not blocked but continues its execution and starts the execution of t2 and t3.
T1, t2 and t3 at this point are executing concurrently and independently of each other. The main thread is not blocked but continues to execute the Main
method. When the main thread reaches Join
call of the three threads, it is blocked and made to wait for the completion of the three threads. Finally, t1, t2, and t3 get completed and the main thread continues the execution of the Main
method and prints out the total time taken by the program.
Since t1, t2, and t3 are executed parallelly, one may get completed before the other in no particular order and again since these threads are executed parallelly the total time taken is just 1 second and not 3 seconds!!!
So this is how multithreading and thus concurrency is achieved in C#.
It is important to touch upon a related concept i.e types of threads:
Foreground thread
Background thread
Let's continue with our above example to understand the two types of threads.
Join
method makes the main thread(the one that calls the thread) wait for the thread on which the join method is called. Had we not used this method, the main thread would have continued its execution. Running the above program with the following changes :
Output :
Key points to note here are:
The main thread is done completing its execution in 0 seconds but t1, t2 and t3 are executing behind the scenes.
The application waits for threads t1, t2 and t3 to get completed before it is finished.
Execution of the main thread continues after it initiates and executes t1, t2 and t3. It doesn't wait for them. However, the application itself waits for the three threads to complete. The reason is these threads are Foreground threads. When a thread is initiated, by default it is a Foreground thread.
Background threads, as distinct from foreground threads get created when we specifically set IsBackgound
value to True
.
A background thread is a kind of thread that runs in the background and does not keep the application running after the main thread ends. Regardless of whether any background threads are running, the application will end if all foreground threads terminate.
Now let's try setting t1, t2 and t3 as background threads and let's see what happens if we don't make the main thread wait for the three threads to get completed.
Output :
As expected the application ends as soon as the main thread ends. Background threads in particular are useful in parallel processing and doing time-consuming operations like I/O, database queries etc without obstructing the main thread.
Conclusion
Concurrency via multithreading is core to modern applications. It makes the application work with higher efficiency, increased throughput, reduced latency and overall better utilization of resources. C# has good support for multithreading using the Thread
Class. To further simplify concurrency, C# introduced the concept of task-based asynchronous programming(TAP) using Task
class, which makes writing asynchronous operations a piece of cake!
Speaking of cake, do you reckon the bakery near your home also has a team of skilled workers all working tirelessly, similar to my friend's restaurant? Well, go check it out and stay tuned for the next blog on Task
, a core concept in C# and .Net framework!