Multithread Programming

First, a little background on processes and threads as defined by the OS is in order.

A thread is the basic unit of execution on the Win32 platform, to which the OS allocates processor time. A process represents a running application that consists of a private virtual address space, code, data, and other OS resources such as files, pipes, and synchronization objects that are visible to the process. A process also contains one or more threads that run in the context of the process. A thread can execute just one task at a time. To perform multiple tasks concurrently, a process can create multiple threads. Even though only one thread can execute at any time,[1] the Windows OS preemptively switches execution from one thread to another. The switching is so fast that all the threads appear to run at the same time.

[1] More specifically, thread execution is processor based. A multiprocessor machine can have multiple threads executing simultaneously.

All threads within a process share the virtual address space and global variables of that process. However, each thread has its own stack. Therefore, when a thread is executing, any program variables that are created on the stack are local to the thread.

A thread is uniquely identified by a numeric value. This numeric identifier is unique only within a process. In other words, two processes can each have a thread that has the same thread ID.

Threads are scheduled for execution based on their priority. Thread priorities specify the relative priority of one thread over another and can be adjusted programmatically. The OS can also adjust the thread priority dynamically.

Often, it is necessary to maintain thread-specific data. However, a static or global variable cannot be used for this purpose because it has the same value across all the threads. To address this problem, the OS provides a feature called thread local storage (TLS). With TLS, it is possible to create a unique copy of a variable for each thread within a process.

This information is in the context of the unmanaged world. Things are slightly different in the managed environment of .NET. For example, a global variable is global only within an application domain, not at the process level. Also, a managed thread can be assigned a name, which is useful for debugging purposes.

The .NET SDK provides classes that deal with thread creation, manipulation, and synchronization. The SDK also provides comprehensive documentation on their usage.

With this brief background information, let's develop a simple application that demonstrates the use of the .NET classes in creating and manipulating threads. As we go along in the chapter, we will pick up any other thread-related information that we need.

A Simple Example

The following code excerpt creates a thread and displays the thread identifier from both the main thread and the spawned thread:

// Project Threads/SimpleThread

using System;
using System.Threading;
public class Foo {

    public static void MyThreadProc() {
      Console.WriteLine("I am in thread: {0}",
        AppDomain.GetCurrentThreadId());
    }
}

class MyApp {
    public static void Main() {
      Console.WriteLine("Main thread: {0}",
        AppDomain.GetCurrentThreadId());
      Thread t = new Thread(
        new ThreadStart(Foo.MyThreadProc));
      t.Start(); // start the thread
      t.Join();  // wait for the thread to finish
    }
}

Namespace System.Threading provides classes and interfaces that deal with multithreaded programming. The class Thread encapsulates the creation and manipulation of a managed thread. This class has a public constructor that takes a delegate ThreadStart as a parameter. Here is the definition of the delegate:

public delegate void ThreadStart();

Essentially, ThreadStart can be used to specify the entry point for the thread. In our example, the entry point is a static method Foo.MyThreadProc. However, a nonstatic method can also be specified as the entry point.

Chaining Multiple Methods

As ThreadStart takes a multicast delegate, it is possible to add more than one method to the delegate. In this case, the thread executes the methods in the order in which they were added.


Creating an instance of thread doesn't spawn the physical thread. To create and execute the thread, you need to call Start on the thread. This method creates the physical OS thread, attaches the Thread object to it (using the TLS), and starts executing the specified entry point method.

Managed Threads and OS Threads

Any code is always executed on a physical OS thread. The .NET runtime creates an OS thread and installs a Thread object in the TLS of the OS thread. A managed thread is essentially an OS thread with the Thread object attached.

The runtime uses a similar technique when an unmanaged thread enters the runtime through, for example, a CCW. The runtime checks if the unmanaged thread already has a Thread object installed in the TLS. If not, it creates one on the fly and installs it.

Note that one should not assume a relationship between the OS thread identifier and the corresponding managed thread. A sophisticated runtime host, for example, can move a managed thread between different OS threads.


To wait for the spawned thread to complete its execution, the main thread calls Join on the instance of the thread. This method blocks the calling thread until the waited-on thread terminates. However, it is possible to specify the time to wait by using an overloaded Join method that takes the wait time as a parameter.

Tada! We just finished writing our first multithreaded program.

Besides Start and Join, there are many other thread-manipulation methods available in the Thread class. Table 8.1 describes some useful methods and their Win32 counterparts for reference.

Table 8.1. Some Useful Methods on the Thread Class
MethodDescriptionWin32 API
Thread.StartStart a threadCreateThread/ResumeThread
Thread.AbortTerminate a thread forcefullyTerminateThread
Thread.SuspendSuspend a thread's executionSuspendThread
Thread.ResumeResume a suspended threadResumeThread
Thread.CurrentThreadReturn the Thread object of the current threadGetCurrentThread
Thread.PriorityAdjust thread's prioritySetThreadPriority
Thread.NameAssign or obtain the name of a managed threadNo equivalent
Thread.IsBackgroundBackground or foreground threadNo equivalent
Thread.JoinWait for the thread to completeWaitForSingleHandle
Thread.ApartmentStateSet or get the COM apartment stateClose to CoInitializeEx

Background Threads

Under .NET, managed threads are classified as foreground threads and background threads. The .NET runtime waits for only the foreground threads to complete before quitting the application. By default, any explicitly spawned thread, such as the one in our previous example, has a foreground status. Therefore, it is not necessary for the main thread to explicitly wait for the spawned thread to complete before quitting the application.

Class Thread provides a bool property, IsBackground, that can be used to check or set the background status of a thread. For example, adding the following line in the thread entry point method of the previous example changes the status of the spawned thread from foreground to background:

Thread.CurrentThread.IsBackground = true;

Setting a thread to background status is useful if you do not wish to wait for the thread to complete when quitting the application.

Aborting a Thread

A thread can be aborted by calling Abort on the Thread object, as illustrated in the following code:

// Project Threads/ThreadAbort

class MyApp {
    public static void Main() {
      Thread t = new Thread(
        new ThreadStart(Foo.MyThreadProc));
      t.Start(); // start the thread
      Thread.Sleep(5*1000);     // give the other thread
                  // a chance to execute
      t.Abort(); // abort the thread
      t.Join();  // wait for the thread to finish
    }
}

When Abort is called on a managed thread, the runtime raises a ThreadAbortException in the thread. ThreadAbortException is a special exception in that although it can be caught by the executing code, it is automatically raised once again at the end of the catch block. Whether or not you catch this exception, the runtime eventually executes all the finally blocks in the call chain and then terminates the thread.

Note the call to Join following the call to Abort on the thread. As the catch and the finally blocks are being executed, the thread may end up executing an unbound computation. Calling Join on the thread guarantees that the thread has indeed been terminated.

Aborting a Thread During an Unmanaged Call

When Abort is called on a managed thread that is executing an unmanaged call into the native DLLs, the common language runtime does not have much control on the thread. However, the common language runtime marks the thread for abort and takes control of the thread when it reenters the managed side.


Incidentally, the code introduces a new static method, Thread.Sleep. Calling this method causes the execution of the current thread to be temporarily suspended. The parameter to the method is used to specify the time in number of milliseconds for which the thread execution should be suspended.

When Thread.Sleep is called, the runtime may switch the context to a different thread. If you intend to suspend the execution of your thread for a very short time, and you do not wish to give up the rest of the time slice allocated to your thread, you can use a static method, Thread.SpinWait. This method causes the executing thread to wait in a tight loop without causing a context switch. The parameter to the method specifies the number of iterations for the loop.

In general, SpinWait is useful on a multiprocessor system. However, it is not easy to compute an appropriate value for the SpinWait iterations. Moreover, finding a case that justifies the use of SpinWait is not easy. My advice is to avoid using this method as much as possible.

Resetting an Abort

When Abort is called on a thread, the thread is terminated after the execution of the catch blocks and the finally blocks. However, .NET also provides an option to the thread being aborted to override the abort directive. This is done by calling the static method Thread.ResetAbort within the catch block, as highlighted in the following code:

// Project Threads/ThreadAbort

public class Foo {
     public static void MyThreadProc() {
       while(true) {
         try {
           Console.WriteLine("I am in thread: {0}",
             AppDomain.GetCurrentThreadId());
           Thread.Sleep(20*1000); // sleep for 20 seconds
         }catch(Exception e) {
           Console.WriteLine("Exception: {0}", e);
           Thread.ResetAbort();
         }finally {
           Console.WriteLine("Executing finally");
         }
       }
     }
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.2.15