Multithreading Overview
Learn what threads are and their importance in responsive applications.
Introduction
Modern applications perform many tasks at the same time. For instance, when we send a photo to our family members with a messenger application, we continue receiving messages while our photo is being uploaded. The method that uploads a photo and the method that receives a message run virtually in a simultaneous manner. Different parts of the system continue to operate at the same time because they run on separate threads. Utilizing several threads is called multithreading.
Note: Threads are unique execution paths with separate control flows. That is, threads don’t affect one another unless they use some common resource, like disk memory.
All code examples we’ve written so far run on a single thread. The subsequent line of code doesn’t run until the previous one finishes executing. In a real-life application, it would look something like this:
- We click the “Download” button and the download starts. The user interface freezes until the download finishes.
Let’s introduce multithreading to this scenario:
- We click the “Download” button and the download starts on a background thread. The user interface stays responsive and doesn’t freeze.
Multithread in a multicore environment
Modern computers run on multicore processors, which involves several processors inside a single chip. Each core can handle computations independently. Running several threads on multiple cores would look as follows:
Because the threads run on two different cores, we can assume that they run in parallel.
Multithread on a single core
Multithreading, by default, doesn’t mean parallel execution. It only means that tasks are executed on multiple threads (control flows). Therefore, it’s false to claim that multithreading is only possible on multicore processors. We can have multiple threads running even on a single core. They, however, don’t run at the same time:
In the illustration above, “Thread 1” executes first for some time while “Thread 2” waits. Then, “Thread 2” gets some processor time while “Thread 1” offloads from the core to wait. This is called context switching. The state of an offloaded thread is stored in memory so that it can be restored when it’s loaded to the CPU sometime later.
Multithread in real life
In practice, even if we have a multicore processor, we can’t be sure that our threads run in parallel. That’s because there are many threads running on a modern-day computer. The number of threads is much larger than the number of cores. Therefore, context switches are inevitable.
We care about context switching because it’s a taxing operation. Each execution-context switch introduces some overhead (like copying the state of the current thread to memory and loading the state of the next thread to the CPU).
Threads in .NET
The main .NET thread functionality is located in the System.Threading namespace. This namespace contains the Thread class that represents a single thread in a program. The Thread class defines a number of methods and properties that allow us to control and obtain thread information. For instance, the static CurrentThread property returns the currently running thread.
Note: Even if we don’t explicitly create threads in our .NET apps, there’ll be at least one thread currently executing. That’s the primary thread where our
Mainmethod runs.
The CurrentThread property returns an instance of the Thread class. Here are some of its properties:
-
ManagedThreadId: This returns the thread’s numeric identifier. -
IsAlive: This indicates whether the thread is currently running. -
IsBackground: This indicates whether the thread is a background thread. -
Priority: This stores aThreadPriorityvalue enum that indicates the priority of the thread. -
ThreadState: This returns the state of the thread, which is a ThreadState value enum.
Note: There are foreground and background threads. Foreground threads keep the app running. When all foreground threads execute, the app stops. In other words, an application keeps running if at least one foreground thread is active. In contrast, a background thread doesn’t affect application execution. When there are no foreground threads, all background threads stop automatically.
Let’s see these properties inside the code:
Besides properties, the Thread class has several methods that let us control thread execution.