
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
592 CHAPTER 18 ■ BUILDING MULTITHREADED APPLICATIONS
Again, the static AddComplete() method will be invoked by the AsyncCallback delegate when the Add() method has completed. If you run this program, you can confirm that the secondary thread is the thread invoking the AddComplete() callback (see Figure 18-3).
Figure 18-3. The AsyncCallback delegate in action
The Role of the AsyncResult Class
You may have noticed in the current example that the Main() method is not caching the IAsyncResult type returned from BeginInvoke() and is no longer calling EndInvoke(). The reason is that the target of the AsyncCallback delegate (AddComplete() in this example) does not have access to the original BinaryOp delegate created in the scope of Main(). While you could simply declare the BinaryOp variable as a static member variable in the class to allow both methods to access the same object, a more elegant solution is to use the incoming IAsyncResult parameter.
The incoming IAsyncResult parameter passed into the target of the AsyncCallback delegate is actually an instance of the AsyncResult class (note the lack of an I prefix) defined in the System. Runtime.Remoting.Messaging namespace. The static AsyncDelegate property returns a reference to the original asynchronous delegate that was created elsewhere. Therefore, if you wish to obtain a reference to the BinaryOp delegate object allocated within Main(), simply cast the System.Object returned by the AsyncDelegate property into type BinaryOp. At this point, you can trigger
EndInvoke() as expected:
//Don't forget to import
//System.Runtime.Remoting.Messaging! static void AddComplete(IAsyncResult itfAR)
{
Console.WriteLine("AddComplete() invoked on thread {0}.", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Your addition is complete");
// Now get the result.
AsyncResult ar = (AsyncResult)itfAR; BinaryOp b = (BinaryOp)ar.AsyncDelegate;
Console.WriteLine("10 + 10 is {0}.", b.EndInvoke(itfAR));
}
Passing and Receiving Custom State Data
The final aspect of asynchronous delegates we need to address is the final argument to the BeginInvoke() method (which has been null up to this point). This parameter allows you to pass additional state information to the callback method from the primary thread. Because this argument is prototyped as a System.Object, you can pass in any type of data whatsoever, as long as the callback method knows what to expect. Assume for the sake of demonstration that the primary thread wishes to pass in a custom text message to the AddComplete() method:

CHAPTER 18 ■ BUILDING MULTITHREADED APPLICATIONS |
593 |
static void Main(string[] args)
{
...
IAsyncResult iftAR = b.BeginInvoke(10, 10, new AsyncCallback(AddComplete),
"Main() thanks you for adding these numbers.");
...
}
To obtain this data within the scope of AddComplete(), make use of the AsyncState property of the incoming IAsyncResult parameter. Notice that an explicit cast will be required; therefore the primary and secondary threads must agree on the underlying type returned from AsyncState.
static void AddComplete(IAsyncResult itfAR)
{
...
// Retrieve the informational object and cast it to string. string msg = (string)itfAR.AsyncState; Console.WriteLine(msg);
}
Figure 18-4 shows the output of the current application.
Figure 18-4. Passing and receiving custom state data
Now that you understand how a .NET delegate can be used to automatically spin off a secondary thread of execution to handle an asynchronous method invocation, let’s turn our attention to directly interacting with threads using the System.Threading namespace.
■Source Code The AsyncCallbackDelegate project is located under the Chapter 18 subdirectory.
The System.Threading Namespace
Under the .NET platform, the System.Threading namespace provides a number of types that enable the direct construction of multithreaded applications. In addition to providing types that allow you to interact with a particular CLR thread, this namespace defines types that allow access to the CLR maintained thread pool, a simple (non–GUI-based) Timer class, and numerous types used to provide synchronized access to shared resources. Table 18-1 lists some of the core members of this namespace. (Be sure to consult the .NET Framework 3.5 SDK documentation for full details.)

594 CHAPTER 18 ■ BUILDING MULTITHREADED APPLICATIONS
Table 18-1. Select Types of the System.Threading Namespace
Type |
Meaning in Life |
Interlocked |
This type provides atomic operations for types that are shared by |
|
multiple threads. |
Monitor |
This type provides the synchronization of threading objects using |
|
locks and wait/signals. The C# lock keyword makes use of a Monitor |
|
type under the hood. |
Mutex |
This synchronization primitive can be used for synchronization |
|
between application domain boundaries. |
ParameterizedThreadStart |
This delegate allows a thread to call methods that take any number |
|
of arguments. |
Semaphore |
This type allows you to limit the number of threads that can access a |
|
resource, or a particular type of resource, concurrently. |
Thread |
This type represents a thread that executes within the CLR. Using |
|
this type, you are able to spawn additional threads in the originating |
|
AppDomain. |
ThreadPool |
This type allows you to interact with the CLR-maintained thread |
|
pool within a given process. |
ThreadPriority |
This enum represents a thread’s priority level (Highest, Normal, etc.). |
ThreadStart |
This delegate is used to specify the method to call for a given thread. |
|
Unlike the ParameterizedThreadStart delegate, targets of |
|
ThreadStart must match a fixed prototype. |
ThreadState |
This enum specifies the valid states a thread may take (Running, |
|
Aborted, etc.). |
Timer |
This type provides a mechanism for executing a method at specified |
|
intervals. |
TimerCallback |
This delegate type is used in conjunction with Timer types. |
|
|
The System.Threading.Thread Class
The most primitive of all types in the System.Threading namespace is Thread. This class represents an object-oriented wrapper around a given path of execution within a particular AppDomain. This type also defines a number of methods (both static and shared) that allow you to create new threads within the current AppDomain, as well as to suspend, stop, and destroy a particular thread. Consider the list of core static members in Table 18-2.
Table 18-2. Key Static Members of the Thread Type
Static Member |
Meaning in Life |
CurrentContext |
This read-only property returns the context in which the thread is currently |
|
running. |
CurrentThread |
This read-only property returns a reference to the currently running thread. |
GetDomain() |
These methods return a reference to the current AppDomain or the ID of this |
GetDomainID() |
domain in which the current thread is running. |
Sleep() |
This method suspends the current thread for a specified time. |
|
|

CHAPTER 18 ■ BUILDING MULTITHREADED APPLICATIONS |
595 |
The Thread class also supports several instance-level members, some of which are shown in Table 18-3.
Table 18-3. Select Instance-Level Members of the Thread Type
Instance-Level Member |
Meaning in Life |
IsAlive |
Returns a Boolean that indicates whether this thread has been started. |
IsBackground |
Gets or sets a value indicating whether or not this thread is a |
|
“background thread” (more details in just a moment). |
Name |
Allows you to establish a friendly text name of the thread. |
Priority |
Gets or sets the priority of a thread, which may be assigned a value from |
|
the ThreadPriority enumeration. |
ThreadState |
Gets the state of this thread, which may be assigned a value from the |
|
ThreadState enumeration. |
Abort() |
Instructs the CLR to terminate the thread as soon as possible. |
Interrupt() |
Interrupts (e.g., wakes ) the current thread from a suitable wait period. |
Join() |
Blocks the calling thread until the specified thread (the one on which |
|
Join() is called) exits. |
Resume() |
Resumes a thread that has been previously suspended. |
Start() |
Instructs the CLR to execute the thread ASAP. |
Suspend() |
Suspends the thread. If the thread is already suspended, a call to |
|
Suspend() has no effect. |
|
|
■Note Aborting or suspending an active thread is generally considered a bad idea. When you do so, there is a chance (however small) that a thread could “leak” its workload when disturbed or terminated.
Obtaining Statistics About the Current Thread
Recall that the entry point of an executable assembly (i.e., the Main() method) runs on the primary thread of execution. To illustrate the basic use of the Thread type, assume you have a new Console Application named ThreadStats. As you know, the static Thread.CurrentThread property retrieves a Thread type that represents the currently executing thread. Once you have obtained the current thread, you are able to print out various statistics:
// Be sure to import the System.Threading namespace. static void Main(string[] args)
{
Console.WriteLine("***** Primary Thread stats *****\n");
//Obtain and name the current thread.
Thread primaryThread = Thread.CurrentThread; primaryThread.Name = "ThePrimaryThread";
//Show details of hosting AppDomain/Context.
Console.WriteLine("Name of current AppDomain: {0}", Thread.GetDomain().FriendlyName);



598CHAPTER 18 ■ BUILDING MULTITHREADED APPLICATIONS
Threading namespace since .NET 1.0, and it can point to any method that takes no arguments and returns nothing. This delegate can be helpful when the method is designed to simply run in the background without further interaction.
The obvious limitation of ThreadStart is that you are unable to pass in parameters for processing. As of .NET 2.0, we were provided with the ParameterizedThreadStart delegate type, which allows a single parameter of type System.Object. Given that anything can be represented as a System.Object, you can pass in any number of parameters via a custom class or structure. Do note, however, that the ParameterizedThreadStart delegate can only point to methods that return void.
Working with the ThreadStart Delegate
To illustrate the process of building a multithreaded application (as well as to demonstrate the usefulness of doing so), assume you have a Console Application (SimpleMultiThreadApp) that allows the end user to choose whether the application will perform its duties using the single primary thread or split its workload using two separate threads of execution.
Assuming you have imported the System.Threading namespace, your first step is to define a type method to perform the work of the (possible) secondary thread. To keep focused on the mechanics of building multithreaded programs, this method will simply print out a sequence of numbers to the console window, pausing for approximately two seconds with each pass. Here is the full definition of the Printer class:
public class Printer
{
public void PrintNumbers()
{
// Display Thread info.
Console.WriteLine("-> {0} is executing PrintNumbers()", Thread.CurrentThread.Name);
// Print out numbers.
Console.Write("Your numbers: "); for(int i = 0; i < 10; i++)
{
Console.Write("{0}, ", i); Thread.Sleep(2000);
}
Console.WriteLine();
}
}
Now, within Main(), you will first prompt the user to determine whether one or two threads will be used to perform the application’s work. If the user requests a single thread, you will simply invoke the PrintNumbers() method within the primary thread. However, if the user specifies two threads, you will create a ThreadStart delegate that points to PrintNumbers(), pass this delegate object into the constructor of a new Thread object, and call Start() to inform the CLR this thread is ready for processing.
To begin, set a reference to the System.Windows.Forms.dll assembly and display a message within Main() using MessageBox.Show() (you’ll see the point of doing so once you run the program). Here is the complete implementation of Main():
static void Main(string[] args)
{
Console.WriteLine("***** The Amazing Thread App *****\n");
Console.Write("Do you want [1] or [2] threads? "); string threadCount = Console.ReadLine();


600 CHAPTER 18 ■ BUILDING MULTITHREADED APPLICATIONS
Before we move on, it is important to note that when you build multithreaded applications (which includes the use of asynchronous delegates to do so) on single CPU machines, you do not end up with an application that runs any faster, as that is a function of a machine’s CPU. When running this application using either one or two threads, the numbers are still displaying at the same pace. In reality, multithreaded applications result in more responsive applications. To the end user, it may appear that this particular program is “faster,” but this is not the case. Threads have no power to make foreach loops execute quicker, to make paper print faster, or to force numbers to be added together at rocket speed. Multithreaded applications simply allow multiple threads to share the workload.
■Source Code The SimpleMultiThreadApp project is included under the Chapter 18 subdirectory.
Working with the ParameterizedThreadStart Delegate
Recall that the ThreadStart delegate can point only to methods that return void and take no arguments. While this may fit the bill in many cases, if you wish to pass data to the method executing on the secondary thread, you will need to make use of the ParameterizedThreadStart delegate type. To illustrate, let’s re-create the logic of the AsyncCallbackDelegate project created earlier in this chapter, this time making use of the ParameterizedThreadStart delegate type.
To begin, create a new Console Application named AddWithThreads and import the System. Threading namespace. Now, given that ParameterizedThreadStart can point to any method taking a System.Object parameter, you will create a custom type containing the numbers to be added:
class AddParams
{
public int a, b;
public AddParams(int numb1, int numb2)
{
a = numb1; b = numb2;
}
}
Next, create a static method in the Program class that will take an AddParams type and print out the summation of each value:
static void Add(object data)
{
if (data is AddParams)
{
Console.WriteLine("ID of thread in Main(): {0}", Thread.CurrentThread.ManagedThreadId);
AddParams ap = (AddParams)data; Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
}
}
The code within Main() is straightforward. Simply use ParameterizedThreadStart rather than
ThreadStart:

CHAPTER 18 ■ BUILDING MULTITHREADED APPLICATIONS |
601 |
static void Main(string[] args)
{
Console.WriteLine("***** Adding with Thread objects *****");
Console.WriteLine("ID of thread in Main(): {0}", Thread.CurrentThread.ManagedThreadId);
// Make an AddParams object to pass to the secondary thread.
AddParams ap = new AddParams(10, 10);
Thread t = new Thread(new ParameterizedThreadStart(Add)); t.Start(ap);
...
}
■Source Code The AddWithThreads project is included under the Chapter 18 subdirectory.
Foreground Threads and Background Threads
Now that you have seen how to programmatically create new threads of execution using the System. Threading namespace, let’s formalize the distinction between foreground threads and background threads:
•Foreground threads have the ability to prevent the current application from terminating. The CLR will not shut down an application (which is to say, unload the hosting AppDomain) until all foreground threads have ended.
•Background threads (sometimes called daemon threads) are viewed by the CLR as expendable paths of execution that can be ignored at any point in time (even if they are currently laboring over some unit of work). Thus, if all foreground threads have terminated, any and all background threads are automatically killed when the application domain unloads.
It is important to note that foreground and background threads are not synonymous with primary and worker threads. By default, every thread you create via the Thread.Start() method is automatically a foreground thread. Again, this means that the AppDomain will not unload until all threads of execution have completed their units of work. In most cases, this is exactly the behavior you require.
For the sake of argument, however, assume that you wish to invoke Printer.PrintNumbers() on a secondary thread that should behave as a background thread. Again, this means that the method pointed to by the Thread type (via the ThreadStart or ParameterizedThreadStart delegate) should be able to halt safely as soon as all foreground threads are done with their work. Configuring such a thread is as simple as setting the IsBackground property to true:
static void Main(string[] args)
{
Console.WriteLine("***** Background Threads *****\n");
Printer p = new Printer(); Thread bgroundThread =
new Thread(new ThreadStart(p.PrintNumbers));
// This is now a background thread. bgroundThread.IsBackground = true; bgroundThread.Start();
}