
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf

C H A P T E R 1 4 ■ B U I L D I N G M U LT I T H R E A D E D A P P L I C AT I O N S |
475 |
Understanding the CLR ThreadPool
The final thread-centric topic we will examine in this chapter is the CLR thread pool. When you invoke a method asynchronously using delegate types (via the BeginInvoke() method), the CLR does not literally create a brand-new thread. For purposes of efficiency, a delegate’s BeginInvoke() method leverages a pool of worker threads that is maintained by the runtime. To allow you to interact with this pool of waiting threads, the System.Threading namespace provides the ThreadPool class type.
If you wish to queue a method call for processing by a worker thread in the pool, you can make use of the ThreadPool.QueueUserWorkItem() method. This method has been overloaded to allow you to specify an optional System.Object for custom state data in addition to an instance of the WaitCallback delegate:
public sealed class ThreadPool
{
...
public static bool QueueUserWorkItem(WaitCallback callBack); public static bool QueueUserWorkItem(WaitCallback callBack,
object state);
}
The WaitCallback delegate can point to any method that takes a System.Object as its sole parameter (which represents the optional state data) and returns nothing. Do note that if you do not provide a System.Object when calling QueueUserWorkItem(), the CLR automatically passes a null value. To illustrate queuing methods for use by the CLR thread pool, ponder the following program, which makes use of the Printer type once again. In this case, however, you are not manually creating an array of Thread types; rather, you are assigning members of the pool to the PrintNumbers() method:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main thread started. ThreadID = {0}", Thread.CurrentThread.GetHashCode());
Printer p = new Printer();
WaitCallback workItem = new WaitCallback(PrintTheNumbers);
// Queue the method 10 times for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(workItem, p);
}
Console.WriteLine("All tasks queued"); Console.ReadLine();
}
static void PrintTheNumbers(object state)
{
Printer task = (Printer)state; task.PrintNumbers();
}
}

476 C H A P T E R 1 4 ■ B U I L D I N G M U LT I T H R E A D E D A P P L I C AT I O N S
At this point, you may be wondering if it would be advantageous to make use of the CLRmaintained thread pool rather than explicitly creating Thread objects. Consider these major benefits of leveraging the thread pool:
•The thread pool manages threads efficiently by minimizing the number of threads that must be created, started, and stopped.
•By using the thread pool, you can focus on your business problem rather than the application’s threading infrastructure.
However, using manual thread management is preferred in some cases, for example:
•If you require foreground threads or must set the thread priority. Pooled threads are always background threads with default priority (ThreadPriority.Normal).
•If you require a thread with a fixed identity in order to abort it, suspend it, or discover it by name.
■Source Code The ThreadPoolApp application is included under the Chapter 14 subdirectory.
That wraps up our examination of multithreaded programming under .NET. To be sure, the System.Threading namespace defines numerous types beyond what I had the space to cover in this chapter. Nevertheless, at this point you should have a solid foundation to build on.
Summary
This chapter began by examining how .NET delegate types can be configured to execute a method in an asynchronous manner. As you have seen, the BeginInvoke() and EndInvoke() methods allow you to indirectly manipulate a background thread with minimum fuss and bother. During this discussion, you were also introduced to the IAsyncResult interface and AsyncResult class type. As you learned, these types provide various ways to synchronize the calling thread and obtain possible method return values.
The remainder of this chapter examined the role of the System.Threading namespace. As you learned, when an application creates additional threads of execution, the result is that the program in question is able to carry out numerous tasks at (what appears to be) the same time. You also examined several manners in which you can protect thread-sensitive blocks of code to ensure that shared resources do not become unusable units of bogus data. Last but not least, you learned that the CLR maintains an internal pool of threads for the purposes of performance and convenience.


478 C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S
Again, to be perfectly clear, if you choose not to concern yourself with the details of CIL code, you are absolutely able to gain mastery of the .NET base class libraries. In many ways, knowledge of CIL is analogous to a C(++) programmer’s understanding of assembly language. Those who know the ins and outs of the low-level “goo” are able to create rather advanced solutions for the task at hand and gain a deeper understanding of the underlying programming (and runtime) environment. So, if you are up for the challenge, let’s begin to examine the details of CIL.
■Note Understand that this chapter is not intended to be a comprehensive treatment of the syntax and semantics of CIL. If you require a full examination of CIL, check out CIL Programming: Under the Hood of .NET by Jason Bock (Apress, 2002).
Examining CIL Directives, Attributes, and Opcodes
When you begin to investigate low-level languages such as CIL, you are guaranteed to find new (and often intimidating-sounding) names for very familiar concepts. For example, at this point in the text, if you were shown the following set of items:
{new, public, this, base, get, set, explicit, unsafe, enum, operator, partial}
you would most certainly understand them to be keywords of the C# language (which is correct). However, if you look more closely at the members of this set, you may be able to see that while each item is indeed a C# keyword, it has radically different semantics. For example, the enum keyword defines a System.Enum-derived type, while the this and base keywords allow you to reference the current object or the object’s parent class, respectively. The unsafe keyword is used to establish a block of code that cannot be directly monitored by the CLR, while the operator keyword allows you to build a hidden (specially named) method that will be called when you apply a specific C# operator (such as the plus sign).
In stark contrast to a higher-level language such as C#, CIL does not just simply define a generic set of keywords, per se. Rather, the token set understood by the CIL compiler is subdivided into three broad categories based on semantic connotation:
•CIL directives
•CIL attributes
•CIL operation codes (opcodes)
Each category of CIL token is expressed using a particular syntax, and the tokens are combined to build a valid .NET assembly.
The Role of CIL Directives
First up, we have a set of well-known CIL tokens that are used to describe the overall structure of a .NET assembly. These tokens are called directives. CIL directives are used to inform the CIL compiler how to define the namespaces(s), type(s), and member(s) that will populate the assembly.
Directives are represented syntactically using a single dot (.) prefix (e.g., .namespace, .class,
.publickeytoken, .override, .method, .assembly, etc.). Thus, if your *.il file (the conventional extension for a file containing CIL code) has a single .namespace directive and three .class directives, the CIL compiler will generate an assembly that defines a single .NET namespace containing three
.NET class types.

C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S |
479 |
The Role of CIL Attributes
In many cases, CIL directives in and of themselves are not descriptive enough to fully express the definition of a given .NET type or type member. Given this fact, many CIL directives can be further specified with various CIL attributes to qualify how a directive should be processed. For example, the .class directive can be adorned with the public attribute (to establish the type visibility), the extends attribute (to explicitly specify the type’s base class), and the implements attribute (to list the set of interfaces supported by the type).
The Role of CIL Opcodes
Once a .NET assembly, namespace, and type set has been defined in terms of CIL using various directives and related attributes, the final remaining task is to provide the type’s implementation logic. This is a job for operation codes, or simply opcodes. In the tradition of other low-level languages, CIL opcodes tend to be completely unpronounceable by us mere humans. For example, if you need to define a string variable, you don’t use a friendly opcode named LoadString, but rather ldstr.
Now, to be fair, some CIL opcodes do map quite naturally to their C# counterparts (e.g., box, unbox, throw, and sizeof). As you will see, the opcodes of CIL are always used within the scope of a member’s implementation, and unlike CIL directives, they are never written with a dot prefix.
The CIL Opcode/CIL Mnemonic Distinction
As just explained, opcodes such as ldstr are used to implement the members of a given type. In reality, however, tokens such as ldstr are CIL mnemonics for the actual binary CIL opcodes. To clarify the distinction, assume you have authored the following method in C#:
static int Add(int x, int y)
{
return x + y;
}
The act of adding two numbers is expressed in terms of the CIL opcode 0X58. In a similar vein, subtracting two numbers is expressed using the opcode 0X59, and the act of allocating a new object on the managed heap is achieved using the 0X73 opcode. Given this reality, understand that the “CIL code” processed by a JIT compiler is actually nothing more than blobs of binary data.
Thankfully, for each binary opcode of CIL, there is a corresponding mnemonic. For example, the add mnemonic can be used rather than 0X58, sub rather than 0X59, and newobj rather than 0X73. Given this opcode/mnemonic distinction, realize that CIL decompilers such as ildasm.exe translate an assembly’s binary opcodes into their corresponding CIL mnemonics:
.method public hidebysig static int32 Add(int32 x, int32 y) cil managed
{
...
//The 'add' token is a friendly mnemonic
//for the 0X58 CIL opcode.
add
...
}
Unless you’re building some extremely low-level .NET software (such as a custom managed compiler), you’ll never need to concern yourself with the literal numeric opcodes of CIL. For all practical purposes, when .NET programmers speak about “CIL opcodes” they’re referring to the set of friendly string token mnemonics (as I’ve done within this text) rather than the underlying binary values.

480 C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S
Pushing and Popping: The Stack-Based Nature of CIL
Higher-level .NET languages (such as C#) attempt to hide low-level grunge from view as much as possible. One aspect of .NET development that is particularly well hidden is the fact that CIL is a completely stack-based programming language. Recall from our examination of the System. Collections namespace (see Chapter 7) that the Stack type can be used to push a value onto a stack as well as pop the topmost value off of the stack for use. Of course, CIL developers do not literally use an object of type System.Collections.Stack to load and unload the values to be evaluated; however, the same pushing and popping mind-set still applies.
Formally speaking, the entity used to hold a set of values is termed the virtual execution stack. As you will see, CIL provides a number of opcodes that are used to push a value onto the stack; this process is termed loading. As well, CIL defines a number of additional opcodes that transfer the topmost value on the stack into memory (such as a local variable) using a process termed storing.
In the world of CIL, it is impossible to access a point of data directly, including locally defined variables, incoming method arguments, or field data of a type. Rather, you are required to explicitly load the item onto the stack, only to then pop it off for later use (keep this point in mind, as it will help explain why a given block of CIL code can look a bit redundant).
To understand how CIL leverages a stack-based model, consider a simple C# method, PrintMessage(), which takes no arguments and returns nothing. Within the implementation of this method, you will simply print out the value of a local string variable to the standard output stream:
public void PrintMessage()
{
string myMessage = "Hello."; Console.WriteLine(myMessage);
}
If you were to examine how the C# compiler translates this method in terms of CIL, you would first find that the PrintMessage() method defines a storage slot for a local variable using the .locals directive. The local string is then loaded and stored in this local variable using the ldstr (load string) and stloc.0 opcodes (which can be read as “store the current value in a local variable at index zero”).
The value (again, at index 0) is then loaded into memory using the ldloc.0 (“load the local argument at index 0”) opcode for use by the System.Console.WriteLine() method invocation (specified using the call opcode). Finally, the function returns via the ret opcode:
.method public hidebysig instance void PrintMessage() cil managed
{
.maxstack 1
//Define a local string variable (at index 0).
.locals init ([0] string myMessage)
//Load a string with the value "Hello."
ldstr " Hello."
//Store string value on the stack in the local variable. stloc.0
//Load the value at index 0.
ldloc.0
// Call method with current value.
call void [mscorlib]System.Console::WriteLine(string) ret
}
■Note As you can see, CIL supports code comments using the double-slash syntax (as well as the /*...*/ syntax, for that matter). As in C#, code comments are completely ignored by the CIL compiler.

C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S |
481 |
Understanding Round-trip Engineering
You are aware of how to use ildasm.exe to view the CIL code generated by the C# compiler. What you may not know, however, is that ildasm.exe allows you to dump the CIL contained within a loaded assembly to an external file. Once you have the CIL code at your disposal, you are free to edit and recompile the code base using the CIL compiler, ilasm.exe.
Formally speaking, this technique is termed round-trip engineering, and it can be useful under a number of circumstances:
•You need to modify an assembly for which you no longer have the source code.
•You are working with a less-than-perfect .NET language compiler that has emitted ineffective CIL code, and you wish to modify the code base.
•You are building COM interoperability assemblies and wish to account for some IDL attributes that have been lost during the conversion process (such as the COM [helpstring] attribute).
To illustrate the process of round-tripping, begin by creating a new C# code file (HelloProgram.cs) using a simple text editor, and define the following class type (you are free to use Visual Studio 2005 as well; however, be sure to delete the AssemblyInfo.cs file to decrease the amount of generated CIL code):
// A simple C# console app. using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello CIL code!"); Console.ReadLine();
}
}
Save your file to a convenient location and compile your program using csc.exe:
csc HelloProgram.cs
Now, open HelloProgram.exe with ildasm.exe and, using the File Dump menu option, save the raw CIL code to a new *.il file (HelloProgram.il) in a convenient location on your hard drive (the default values of the resulting dialog box are fine as is). Now you are able to view this file using your text editor of choice. Here is the (slightly reformatted and annotated) result:
//Referenced Assemblies.
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
//Our assembly.
.assembly HelloProgram
{
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module HelloProgram.exe
.imagebase 0x00400000
.file alignment 0x00000200

482C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S
.stackreserve 0x00100000
.subsystem 0x0003
.corflags 0x00000001
// Definition of Program class.
.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object
{
.method private hidebysig static void Main(string[] args) cil managed
{
//Marks this method as the entry point of the
//executable.
.entrypoint
.maxstack 8 IL_0000: nop
IL_0001: ldstr "Hello CIL code!"
IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop
IL_000c: call string [mscorlib]System.Console::ReadLine() IL_0011: pop
IL_0012: ret
}
// The default constructor.
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8 IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret
}
}
First, notice that the *.il file opens by declaring each externally referenced assembly the current assembly is compiled against. Here, you can see a single .assembly extern token set for the always present mscorlib.dll. Of course, if your class library made use of types within other referenced assemblies, you would find additional .assembly extern directives.
Next, you find the formal definition of your HelloProgram.exe assembly, which has been assigned a default version of 0.0.0.0 (given that you did not specify a value using the [AssemblyVersion] attribute). The assembly is further described using various CIL directives (such as .module, .imagebase, and so forth).
After documenting the externally referenced assemblies and defining the current assembly, you find a definition of the Program type. Note that the .class directive has various attributes (many of which are optional) such as extends, which marks the base class of the type:
.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object
{ ... }
The bulk of the CIL code implements the class’s default constructor and the Main() method, both of which are defined (in part) with the .method directive. Once the members have been defined using the correct directives and attributes, they are implemented using various opcodes.
It is critical to understand that when interacting with .NET types (such as System.Console) in CIL, you will always need to use the type’s fully qualified name. Furthermore, the type’s fully qualified name must always be prefixed with the friendly name of the defining assembly (in square brackets). Consider the CIL implementation of Main():

C H A P T E R 1 5 ■ U N D E R S TA N D I N G C I L A N D T H E R O L E O F DY N A M I C A S S E M B L I E S |
483 |
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack |
8 |
IL_0000: |
nop |
IL_0001: |
ldstr "Hello CIL code!" |
IL_0006: |
call void [mscorlib]System.Console::WriteLine(string) |
IL_000b: |
nop |
IL_000c: call string [mscorlib]System.Console::ReadLine() |
|
IL_0011: |
pop |
IL_0012: |
ret |
}
The implementation of the default constructor in terms of CIL code makes use of yet another “load-centric” instruction (ldarg.0). In this case, the value is loaded onto the stack as not a custom variable specified by us, but the current object reference (more details on this later). Also note that the default constructor explicitly makes a call to the base class constructor:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8 IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret
}
The Role of CIL Code Labels
One thing you certainly have noticed is that each line of implementation code is prefixed with a token of the form IL_XXX: (e.g., IL_0000:, IL_0001:, and so on). These tokens are called code labels and may be named in any manner you choose (provided they are not duplicated within the same scope). When you dump an assembly to file using ildasm.exe, it will automatically generate code labels that follow an IL_XXX: naming convention. However, you may change them to reflect a more descriptive marker:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
Nothing_1: nop
Load_String: ldstr "Hello CIL code!"
PrintToConsole: call void [mscorlib]System.Console::WriteLine(string) Nothing_2: nop
WaitFor_KeyPress: call string [mscorlib]System.Console::ReadLine() RemoveValueFromStack: pop
Leave_Function: ret
}
The truth of the matter is that most code labels are completely optional. The only time code labels are truly useful (and mandatory) is when you are authoring CIL code that makes use of various branching or looping constructs. Given this, you can remove these autogenerated labels altogether with no ill effect:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8