Reviewing Processes and Threads Under Traditional Win32The concept of processes and threads has existed within Windows-based operating systems well before the release of the .NET platform. Simply put, process is the term used to describe the set of resources (such as external code libraries and the primary thread) as well as the necessary memory allocations used by a running application. For each *.exe loaded into memory, the operating system creates a separate and isolated memory partition (aka process) for use during its lifetime. Using this approach to application isolation, the result is a much more robust and stable runtime environment, given that the failure of one process does not effect the functioning of another.Now, every Win32 process is assigned a unique process identifier (PID), and may be independently loaded and unloaded by the operating system as necessary (as well as programmatically using Win32 API calls). As you may be aware, the Processes tab of the Task Manager utility (activated via the Ctrl+Shift+Esc keystroke combination) allows you to view statistics regarding the set of processes running on a given machine, including its PID and image name (Figure 10-1). (If you do not see a PID column, select the View | Select Columns menu and check the PID box.)Figure 10-1. The Windows Task ManagerEvery Win32 process has at least one main "thread" that functions as the entry point for the application. Formally speaking, the first thread created by a process' entry point is termed the primary thread. Simply put, a thread is a specific path of execution within a Win32 process. Traditional Windows applications define the WinMain() method as the application's entry point. On the other hand, console application provides the main() method for the same purpose.Processes that contain a single primary thread of execution are intrinsically "threadsafe," given the fact that there is only one thread that can access the data in the application at a given time. However, a single-threaded process (especially one that is GUIbased) will often appear a bit unresponsive to the user if this single thread is performing a complex operation (such as printing out a lengthy text file, performing an exotic calculation, or attempting to connect to a remote server thousands of miles away). Given this potential drawback of single-threaded applications, the Win32 API makes it is possible for the primary thread to spawn additional secondary threads (also termed worker threads) in the background, using a handful of Win32 API functions such as CreateThread(). Each thread (primary or secondary) becomes a unique path of execution in the process and has concurrent access to all shared points of data.As you may have guessed, developers typically create additional threads to help improve the program's overall responsiveness. Multithreaded processes provide the illusion that numerous activities are happening at more or less the same time.For example, an application may spawn a worker thread to perform a labor-intensive unit of work (again, such as printing a large text file). As this secondary thread is churning away, the main thread is still responsive to user input, which gives the entire process the potential of delivering greater performance. However, this may not actually be the case: using too many threads in a single process can actually degrade performance, as the CPU must switch between the active threads in the process (which takes time).In reality, it is always worth keeping in mind that multithreading is most commonly an illusion provided by the operating system. Machines that host a single CPU do not have the ability to literally handle multiple threads at the same exact time. Rather, a single CPU will execute one thread for a unit of time (called a time-slice) based on the thread's priority level. When a thread's time-slice is up, the existing thread is suspendedto allow another thread to perform its business. For a thread to remember what was happening before it was kicked out of the way, each thread is given the ability to write to Thread Local Storage (TLS) and is provided with a separate call stack, as illustrated inFigure 10-2.Figure 10-2. The Win32 process / thread relationshipNOTE The newest Intel CPUs have an ability called hyperthreading that allows a single CPU to handle multiple threads simultaneously under certain circumstances. See http://www.intel.com/info/ hyperthreading for more details.Interacting with Processes Under the .NET PlatformAlthough processes and threads are nothing new, the manner in which we interact with these primitives under the .NET platform has changed quite a bit (for the better). To pave the way to understanding the world of building multithreaded assemblies, let's begin by checking out how processes have been altered to accommodate the needs of the CLR.The System.Diagnostics namespace defines a number of types that allow you to programmatically interact with processes and various diagnostic-related types such as the system event log and performance counters. For the purposes of this chapter, we are only concerned with the process-centric types, defined in Table 10-1.Table 10-1. Select Members of the System.Diagnostics Namespace
The System.Diagnostics.Process type allows you to identify the running processes on a given machine (local or remote). The Process class also provides members that allow you to programmatically start and terminate processes, establish a process' priority level, and obtain a list of active threads and/or loaded modules within a given process. Table 10-2 illustrates some (but not all) of the key members of System.Diagnostics.Process.Table 10-2. Select Members of the Process Type