In the managed world of the common language runtime, the fundamental unit of execution is the thread. A managed application begins its life as a single thread but can spawn additional threads to help it carry out its appointed mission. Threads running concurrently share the CPU (or CPUs) by using scheduling algorithms provided by the system. To an observer, it appears as if all the threads are running at once. In reality, they simply share processor time鈥攁nd do so very efficiently.
Why would an application spawn additional threads? Multithreading is a mechanism for performing two or more tasks concurrently. Tasks executed by threads running side by side on a single-CPU system don鈥檛 execute any faster; CPU time is, after all, a finite resource. They do, however, execute asynchronously with respect to one another, allowing independent units of work to be performed in parallel. Multithreading is also a vehicle for taking advantage of multiple CPUs. A single-threaded application uses just one processor at a time. A multithreaded application can have different threads running on different processors鈥攑rovided, of course, that the operating system supports it. Versions of Windows built on the NT kernel support multiple processors using a strategy called symmetric multiprocessing (SMP). Versions that derive from Windows 95 do not.
The canonical example for demonstrating the benefits of multithreading is a single-threaded GUI application that enters a lengthy computational loop. While the application鈥檚 one and only thread is busy crunching numbers, it ignores the message queue that serves as the conduit for user input. Until the computation ends, the application鈥檚 user interface is frozen. A multithreaded design can solve this problem by relegating the computational work to a background thread. With the primary thread free to service the message queue, the application remains responsive to user input even while the computation is going on. This chapter鈥檚 first two sample programs model this very scenario.
Multithreading isn鈥檛 for the faint of heart. Multithreaded applications are difficult to write and debug because the parallelism of concurrently running threads adds an extra layer of complexity to a program鈥檚 code. If one thread can write to a data structure at the same time that another thread can read from it, for example, the threads probably need to be synchronized to prevent reads and writes from overlapping. What happens if they鈥檙e not synchronized? Data could be corrupted or spurious exceptions could be thrown. The most difficult aspect of threading is that bugs in multithreaded code tend to be highly dependent on timing and therefore difficult to reproduce. Experienced developers know that you can never be entirely sure that a multithreaded program is bug-free. The inherent parallelism of multithreaded code, multiplied by the uncertainty of how much or how little processor time individual threads are allotted, yields a figure that is too high for the human mind to comprehend.
Aspersions aside, if you set out to create a multithreaded program, you鈥檇 better know what you鈥檙e getting into. This chapter describes the .NET Framework鈥檚 threading API. It begins with an overview of how to start, stop, and manipulate threads. It continues with a treatment of thread synchronization鈥攚hy it鈥檚 important and what devices the framework places at your disposal for coordinating the actions of concurrently running threads. All things considered, I think you鈥檒l agree that threading is one of the most interesting鈥攁nd potentially useful鈥攆eatures of the CLR and the .NET Framework class library.