The Basics
In this and the following articles, I will discuss in some details how to write multithreaded applications using C# and .NET Framework. That means to write code in C# which spawns multiple threads to execute tasks on different threads simultaneously, while at the same time ensuring that these threads do not conflict with each other, do not conflict the general program environment, they are synchronized with each other, and there is full control over what these threads do and how they do it.
In C# multithreading, there are multiple ways of doing the same thing, each having its own pros and cons. Wherever possible I will try to compare these alternative ways and their pros and cons. I do not claim that my views are completely accurate, there is always a scope of improvement and correction.
In this part, we start by looking at the simple example of a multithreaded code. If you are familiar with the basics, you may skip this part.
There are many occasions when writing a multithreaded application makes sense. Most of the computers (and I am not referring to the servers) these days are multi-core processor based, which begs for code paths that can be executed parallel for better CPU utilization. Applications that are not responsive to the end user's actions are often shunned, and rightly so. Client-Server applications that are computations-intensive are natural candidates for asynchronous processing, which is easily achieved by multithreading. However, there are also reasons for not writing a multithreaded program.
Often designing and writing a multithreaded program that behaves correctly under all scenarios of the application is tough, if not impossible. It is all too easy to mess up with the synchronizing code or to rush two threads to deadlocking each other. More subtle ways of causing troubles are to write code that causes race conditions, resulting in unexpected program behavior, hard to reproduce bugs requiring hours of debugging efforts. Sometimes it is simply not required by the application to perform parallel tasks. Some other times, the system may not have multiple processors so there is no significant improvement in performance upon multithreading. So before you decide to write your code as multithreaded, be sure you actually have looked into the reasons for it to be this way.
One example to write a multithreaded application is, say you are processing some background task of copying thousands of files and want to show your user the progress of the status by showing file being copied on the UI. You may have two threads running in this application. First, the default (main) thread is updating your UI and second thread in the background copying the file.
OK, now right into the stuff.
In C#, the Thread class represents a thread. The constructor of the Thread class takes a method that will be executed when a thread starts. In the code listed below, I create two threads and call the Start method to start executing them simultaneously. One thread generates even numbers and another thread generates odd numbers.
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Threading;
-
- namespace MultiThread
- {
- class Program
- {
- static void Main(string[] args)
- {
- Thread evenCounter = new Thread( new ThreadStart( ListEvenNumbers));
- Thread oddCounter = new Thread(new ThreadStart( ListOddNumbers));
- evenCounter.Start();
- oddCounter.Start();
- Console.ReadKey();
- }
-
- private static void ListEvenNumbers()
- {
- for (int i = 2; i < 1000; i = i + 2)
- {
- Console.WriteLine(i.ToString());
- }
- }
-
- private static void ListOddNumbers()
- {
- for(int i=3 ;i< 1000;i = i + 2)
- Console.WriteLine( i.ToString());
- }
- }
- }
If you run this code, you will see the following output. (Don't worry if your output is not exactly the same as here, just believe me it should be like this !)
What is happening here? Well, the program starts with the main method, as usual. This is the main thread of execution. The first line creates a new thread. ( but doesn't start it just yet), then goes ahead to create another thread, then comes back to the first thread and kicks it to start, and then the same to the second one, then quietly waits till the two thread complete their tasks, before switching itself off.
Please note a few important things here: A thread is a program execution context which the runtime executes. More concretely, a thread will have a set of code lines to execute, in a context. In any C# application, there is always the main thread. This is where the execution begins first. As this main thread executes, it may create new threads, and thus create new contexts of code execution. A running thread executes independent of other running threads, and has its own identity to the runtime. A thread which is running at one moment can be paused, restarted, or terminated by the runtime, either by the runtime's own rules or as dictated by the programmer's code. So in the above code snippet, when the main thread creates and starts two child threads and assigns the two methods on each of the threads, the two child threads become execution contexts in themselves each and the runtime gets busy executing them, independently of the main thread. What that essentially means is that executions of the methods ListEvenNumbers and ListOddNumbers do not disturb each other, and do not disturb the execution of the main thread also (though the main thread is affected by the two child threads, we will come to that in a moment).
This is evident from the output. Had we not created the two child threads to run the two methods on them each, we would have seen a different output. For example, the below code tries to execute the same two methods, without using secondary threads. See the output, contrast it with the output seen for above code.
(for brevity's sake, I am showing just the main method, rest else remains the same )
- static void Main(string[] args)
- {
- ListEvenNumbers();
- ListOddNumbers();
- Console.ReadKey();
- }
Now the output should be something like this:
As you can see, the first method had to finish first before the second method could start, while in the case of child threads example, the two methods started executed independently of each other.
This takes some time for beginners to understand, but focus on the 'independent' execution of the methods above, and you will soon realize the importance of threads.