The concept of threads is central to the inner workings of most operating systems, including Windows, but relatively few programmers even know what they are?let alone how to take advantage of them. Understanding threads, using modern operating system’s multithreading capabilities properly, is a fundamental step toward creating fast, responsive applications. The .NET Framework makes creating multi-threaded applications easier than ever. In this article you’ll see what threads are, how threading works, and how you can use them in your applications.
Why Multithreading?
To understand the power of multithreading you need to know something about how the Windows operating system works under the hood. Windows is a preemptive multitasking operating system. The system CPU can do only one thing at a time, but to give the illusion that multiple processes are running simultaneously the operating system splits the CPU time between the various running processes.
The term preemptive means that the operating system determines when each task executes and for how long. Preemptive multitasking differs from cooperative multitasking where each task decides how long it will execute. Preemptive multitasking prevents one task from taking up all the processor’s time.
For example, you might think you can edit a word processing document, print a spreadsheet, and download an MP3 file from the Web all at the same time, but in actuality, the operating system allocates small “slices” of CPU time to each of these processes. Because the CPU is very fast, humans are very slow, and the time slices can be extremely small, all three processes appear to run simultaneously.
On a multi-processor system things are a bit more complicated but the basic idea is the same?the operating system divides the time of the CPUs among the processes that need it. Figure 1 illustrates the principle of multitasking.
![]() |
|
Figure 1: A multitasking operating system divides the CPU’s time between all running processes. |
Each process has a priority that determines how the operating system allocates CPU time to it relative to other processes. Schemes for allocating process priorities differ among OS’s, but Windows 2000 has four priority levels. In descending order, these priorities are:
- Real time: The highest priority. These processes preempt all processes with lower priority. Real time priority is reserved for processes that cannot suffer even minor interruptions, such as streaming video and games with complex graphics.
- High priority: Used for time-critical processes that must execute immediately (or almost immediately) for proper operating system functionality. The Windows Task List is an example of a high-priority process. It is assigned high priority because it must display immediately when requested by the user regardless of anything else the operating system is doing. Only real-time priority threads can interrupt a high priority thread.
- Normal priority: Used for processes that have no special CPU scheduling needs. Word processing and background printing are examples of normal priority processes.
- Idle priority: Used for processes that run only when the system is otherwise idle. A screen saver is a good example of this priority level.
These thread priorities are those defined for the Windows 2000 operating system. When you are assigning a priority to a thread in .NET the names that are used are different, but the meanings are clear.
Here’s where the concept of threads becomes important. A thread is the unit, or entity, to which the operating system assigns CPU time. Normally a program or process is associated with a single thread. The process must accomplish everything it needs to do?drawing the user interface, responding to user input, writing files, calculating results, everything?during the CPU time that Windows allocates to its single thread. For most applications this works just fine.
Windows also supports multi-threaded processes. As the name suggests, a multi-threaded process is one in which the program divides its tasks among two or more separate threads. Because Window allocates processor time to threads and not to processes, a multi-threaded process gets more than the usual share of CPU time.
So is the purpose of multithreading simply to speed up a program? No, although it can have that effect, particularly on a multiprocessor system. The most important use of multithreading has to do with the program’s responsiveness to users. There are few things more frustrating to users than a program that does not respond immediately to mouse and keyboard input. Yet, when a single-threaded program has lengthy calculations or I/O activities going on at the same time this is exactly what is going to happen. The program’s one thread must handle user interaction and the calculations, which often causes the user interaction to become sluggish.
Therefore, when your program must respond to user input at (perceptually) the same time that it is performing one or more other tasks, you should consider multithreading. By assigning calculations or I/O to one thread, and user interaction to another thread, you can create a program that responds to user actions efficiently while also performing the required data processing.
There’s Always a Downside
Using multiple threads is not all roses, however. As with almost everything else in life, a penalty accompanies the advantages. There are several factors you should consider.
First, keeping track of and switching between threads consumes memory resources and CPU time. Each time the CPU switches to another thread, the state of the current thread must be saved (so it can be resumed again later) and the saved state of the new thread must be restored. With too many threads any responsiveness advantages you hoped to gain may be partially nullified by the extra load placed on the system.
Second, programming with multiple threads can be complex. Creating a single extra thread to handle some background calculations is fairly straightforward, but implementing many threads is a demanding task and can be the source of many hard-to-find bugs. My approach to these potential problems is to use multithreading only when it provides a clear advantage, and then to use a few threads as possible.
Third is the question of shared resources. Because they’re running in the same process, the threads of a multi-threaded program have access to that process’s resources, including global, static, and instance fields. Also, threads may need to share other resources such as communications ports and file handles. You must synchronize the threads in most multi-threaded applications to prevent conflicts when accessing resources, such as deadlocks (when two threads stop as each waits for the other to terminate).
For example, suppose that Thread A is responsible for obtaining data over a network, and Thread B is responsible for performing calculations with that data. It might seem like a good idea to require Thread A to wait for Thread B to complete (so the data is not updated in the middle of a calculation), and also to require Thread B to wait for Thread A to complete (so the latest data is used in the calculations). If coded improperly the result will be two threads that never execute. The .NET Framework provides classes to control thread synchronization, but even so, multithreading introduces another level of programming complexity.
The .NET Framework provides two methods to implementing a new thread in your program. The first, which is easier to use but does not provide as much control of the thread, makes use of the thread pool. The thread pool is a queue of idle threads managed by the .NET runtime . To assign some code to a new thread, your program sends a request to the thread pool. The pool manager assigns an available thread to the code, executes it, and then returns the thread to the pool when the code completes. To add a thread to the thread pool, you call the static ThreadPool.QueueUserWorkItem method. Like all threading-related classes, the ThreadPool class is in the System.Threading namespace. The syntax for this method is:
ThreadPool.QueueUserWorkItem( new WaitCallback(ThreadCode), Done);
The ThreadCode argument is the name of the method in your program where the new thread should start executing. The Done argument is an AutoResetEvent object that you use to signal the thread pool manager that you’re done with the thread. The method specified by the ThreadCode argument must meet the following two requirements:
- Its signature must match that of the WaitCallback class. Specifically, it must take one argument of type Object, and must have a void return value (in VB.NET, use a Sub method rather than a Function).
- When the thread code completes it must inform the thread pool that it is finished.
The following example shows a method that meets the two requirements for starting thread execution:
static void MyThreadOperation(object state) { //Code to be executed by the thread goes here, including //calls to other methods as required. //Signal that the thread has finished executing. ((AutoResetEvent)state).Set(); }
You would start this thread running with the following code:
AutoResetEvent IsDone = new AutoResetEvent(false); ThreadPool.QueueUserWorkItem (new WaitCallback(MyThreadOperation), IsDone);
A Demonstration
The following short program demonstrates how to create and use a thread through the thread pool, and also illustrates how multithreading can improve a program’s responsiveness. The plan is to create a time-consuming process that can be run two ways?as a separate thread or as part of the program’s default thread. This process mimics complex calculations or I/O for which a real-world program might use multithreading. You can elect to run the time-consuming process on a single thread or in its own thread. While the long process is running you can still edit the text in a text box control to experiment with the difference in the program’s responsiveness to user input when you add the second thread. Figure 2 shows the sample application’s interface.
![]() |
Using the ThreadPool class for new threads is quite easy, as you have seen; however you may need more control over the threads you create. For example, you may need to assign a priority to a thread, to pause and restart the thread, or to stop it before it has completed. You may also need to synchronize threads, as discussed earlier, to prevent conflicts when accessing resources. When you need this level of control you must use the Thread class. Each instance of the Thread class represents a separate thread. The class members let you set the thread’s priority, start, pause, and stop it, and also determine its current state. The details of using the Thread class are beyond the scope of this article, but you can find complete details as well as demonstration programs in the .NET Framework documentation.
Windows is a preemptive multitasking operating system, giving it the ability to appear to perform multiple tasks simultaneously. The basic unit for multitasking is the thread. A process, or program, typically has only a single thread. In some situations it can be advantageous for a program to have two or more threads executing at once, a technique called multithreading. In particular, multithreading can greatly improve a program’s responsiveness to the user while time-consuming calculations or I/O tasks are being performed. The .NET Framework makes the implementation of multithreading relatively easy, and while multithreading is not a cure-all for performance problems it is definitely a tool that every programmer should have in his or her toolbox.
Share the Post:
![]() ![]() The Best Mechanical Keyboards For Programmers: Where To Find Them
May 29, 2023
When it comes to programming, a good mechanical keyboard can make all the difference. Naturally, you would want one of the best mechanical keyboards for programmers. But with so many ![]() ![]() The Digital Panopticon: Is Big Brother Always Watching Us Online?
May 26, 2023
In the age of digital transformation, the internet has become a ubiquitous part of our lives. From socializing, shopping, and learning to more sensitive activities such as banking and healthcare, ![]() ![]() Embracing Change: How AI Is Revolutionizing the Developer’s Role
May 25, 2023
The world of software development is changing drastically with the introduction of Artificial Intelligence and Machine Learning technologies. In the past, software developers were in charge of the entire development ![]() ![]() The Benefits of Using XDR Solutions
May 24, 2023
Cybercriminals constantly adapt their strategies, developing newer, more powerful, and intelligent ways to attack your network. Since security professionals must innovate as well, more conventional endpoint detection solutions have evolved ![]() ![]() How AI is Revolutionizing Fraud Detection
May 23, 2023
Artificial intelligence – commonly known as AI – means a form of technology with multiple uses. As a result, it has become extremely valuable to a number of businesses across ![]() ![]() Companies Leading AI Innovation in 2023
May 22, 2023
Artificial intelligence (AI) has been transforming industries and revolutionizing business operations. AI’s potential to enhance efficiency and productivity has become crucial to many businesses. As we move into 2023, several ![]() ![]() Step-by-Step Guide to Properly Copyright Your Website
May 18, 2023
Creating a website is not easy, but protecting your website is equally important. Implementing copyright laws ensures that the substance of your website remains secure and sheltered. Copyrighting your website ![]() ![]() Fivetran Pricing Explained
May 17, 2023
One of the biggest trends of the 21st century is the massive surge in analytics. Analytics is the process of utilizing data to drive future decision-making. With so much of ![]() ![]() Kubernetes Logging: What You Need to Know
May 16, 2023
Kubernetes from Google is one of the most popular open-source and free container management solutions made to make managing and deploying applications easier. It has a solid architecture that makes ![]() ![]() Why Is Ransomware Such a Major Threat?
May 15, 2023
One of the most significant cyber threats faced by modern organizations is a ransomware attack. Ransomware attacks have grown in both sophistication and frequency over the past few years, forcing ![]() ![]() Tools You Need to Make a Data Dictionary
May 12, 2023
Data dictionaries are crucial for organizations of all sizes that deal with large amounts of data. they are centralized repositories of all the data in organizations, including metadata such as ![]() ![]() 10 Software Development Tips to Get Early Funding for your Startup
May 11, 2023
If you’re thinking about a startup, it’s likely you need to raise an initial round of funding for your venture. This article covers some of the very early development techniques |