
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
444 C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S
Given that the SportsCar class has not been qualified with a context attribute, the CLR has allocated sport and sport2 into context 0 (i.e., the default context). However, the SportsCarTS object is loaded into a unique contextual boundary (which has been assigned a context ID of 1), given the fact that this context-bound type was adorned with the [Synchronization] attribute.
■Source Code The ContextManipulator project is included under the Chapter 13 subdirectory
Summarizing Processes, AppDomains, and Context
At this point, you hopefully have a much better idea about how a .NET assembly is hosted by the CLR. To summarize the key points,
•A .NET process hosts one to many application domains. Each AppDomain is able to host any number of related .NET assemblies and may be independently loaded and unloaded by the CLR (or programmatically via the System.AppDomain type).
•A given AppDomain consists of one to many contexts. Using a context, the CLR is able to place a “special needs” object into a logical container, to ensure that its runtime requirements are honored.
If the previous pages have seemed to be a bit too low level for your liking, fear not. For the most part, the .NET runtime automatically deals with the details of processes, application domains, and contexts on your behalf. The good news, however, is that this information provides a solid foundation for understanding multithreaded programming under the .NET platform. Before we turn our attention to the System.Threading namespace, though, we’ll examine how the CLR itself is hosted by the Win32 OS.
Hosting the Common Language Runtime
To the end user, running a .NET executable is achieved simply by double-clicking the *.exe in Windows Explorer (or activating an associated shortcut). As you recall from Chapter 1, however, the .NET Framework is not (currently) incorporated directly into the Windows OS, but sits on top of the OS itself. When you install Visual Studio 2005 (or the .NET Framework 2.0 SDK) on your development machine, the .NET runtime environment (including the necessary base class libraries) is installed as well. Also recall that Microsoft provides a freely distributable .NET runtime setup program (dotnetfx.exe) to configure end user machines to host .NET assemblies.
Given that the Windows OS does not natively understand the format of a .NET assembly, it should be clear that various steps occur in the background when an executable assembly is activated. Under the Windows XP OS, the basic steps are as follows (do recall from Chapter 11 that all .NET assemblies contain Win32 header information):
1.The Windows OS loads the executable binary file into memory.
2.The Windows OS reads the embedded WinNT header to determine if the binary file is a .NET assembly (via the IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR flag).
3.If the image is a .NET assembly, mscoree.dll is loaded.
4.mscoree.dll then loads one of two implementations of the CLR (mscorwks.dll or mscorsvr.dll).
5.At this point, the CLR takes over the show, performing all .NET-centric details (finding external assemblies, performing security checks, processing CIL code, performing garbage collections, etc.).



C H A P T E R 1 3 ■ P R O C E S S E S, A P P D O M A I N S, C O N T E X T S, A N D C L R H O S T S |
447 |
Additional CLR Hosts
The process just defined qualifies the basic steps taken by the Windows OS to host the CLR when an executable assembly is activated. However, Microsoft provides many applications that bypass this out-of-the-box behavior in favor of loading the CLR programmatically. For example, Microsoft Internet Explorer can natively host custom Windows Forms controls (the managed equivalent of the now legacy ActiveX controls). The latest version of Microsoft SQL Server (code-named Yukon and officially called SQL Server 2005) also has the ability to directly host the CLR internally.
As a final note, Microsoft has defined a set of interfaces that allow developers to build their own custom CLR host. This may be done using straight C/C++ code or via a COM type library (mscoree.tlb). While the process of building a custom CLR host is surprisingly simple (especially using the COM type library), this topic is outside the scope of this text. If you require further information, you can find numerous articles online (just do a search for “CLR hosts”).
Summary
The point of this chapter was to examine exactly how a .NET executable image is hosted by the
.NET platform. As you have seen, the long-standing notion of a Win32 process has been altered under the hood to accommodate the needs of the CLR. A single process (which can be programmatically manipulated via the System.Diagnostics.Process type) is now composed of multiple application domains, which represent isolated and independent boundaries within a process. As you have seen, a single process can host multiple application domains, each of which is capable of hosting and executing any number of related assemblies.
Furthermore, a single application domain can contain any number of contextual boundaries. Using this additional level of type isolation, the CLR can ensure that special-need objects are handled correctly. The chapter concluded by examining the details regarding how the CLR is hosted by the Win32 OS.



450 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
Although active threads can be moved between AppDomain boundaries, a given thread can execute within only a single application domain at any point in time (in other words, it is impossible for a single thread to be doing work in more than one AppDomain). When you wish to programmatically gain access to the AppDomain that is hosting the current thread, call the static
Thread.GetDomain() method:
private static void ExtractAppDomainHostingThread()
{
// Obtain the AppDomain hosting the current thread.
AppDomain ad = Thread.GetDomain();
}
A single thread may also be moved into a particular context at any given time, and it may be relocated within a new context at the whim of the CLR. When you wish to obtain the current context a thread happens to be executing in, make use of the static Thread.CurrentContext property:
private static void ExtractCurrentThreadContext()
{
//Obtain the Context under which the
//current thread is operating.
Context ctx = Thread.CurrentContext;
}
Again, the CLR is the entity that is in charge of moving threads into (and out of) application domains and contexts. As a .NET developer, you can usually remain blissfully unaware where a given thread ends up (or exactly when it is placed into its new boundary). Nevertheless, you should be aware of the various ways of obtaining the underlying primitives.
The Problem of Concurrency and the Role of Thread
Synchronization
One of the many “joys” (read: painful aspects) of multithreaded programming is that you have little control over how the underlying operating system or the CLR makes use of its threads. For example, if you craft a block of code that creates a new thread of execution, you cannot guarantee that the thread executes immediately. Rather, such code only instructs the OS to execute the thread as soon as possible (which is typically when the thread scheduler gets around to it).
Furthermore, given that threads can be moved between application and contextual boundaries as required by the CLR, you must be mindful of which aspects of your application are thread-volatile (e.g., subject to multithreaded access) and which operations are atomic (thread-volatile operations are the dangerous ones!). To illustrate, assume a thread is invoking a method of a specific object. Now assume that this thread is instructed by the thread scheduler to suspend its activity, in order to allow another thread to access the same method of the same object.
If the original thread was not completely finished with the current operation, the second incoming thread may be viewing an object in a partially modified state. At this point, the second thread is basically reading bogus data, which is sure to give way to extremely odd (and very hard to find) bugs, which are even harder to replicate and debug.
Atomic operations, on the other hand, are always safe in a multithreaded environment. Sadly, there are very few operations in the .NET base class libraries that are guaranteed to be atomic. Even the act of assigning a value to a member variable is not atomic! Unless the .NET Framework 2.0 SDK documentation specifically says an operation is atomic, you must assume it is thread-volatile and take precautions.
At this point, it should be clear that multithreaded application domains are in themselves quite volatile, as numerous threads can operate on the shared functionality at (more or less) the same time. To protect an application’s resources from possible corruption, .NET developers must make use of

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 |
451 |
any number of threading primitives (such as locks, monitors, and the [Synchronization] attribute) to control access among the executing threads.
Although the .NET platform cannot make the difficulties of building robust multithreaded applications completely disappear, the process has been simplified considerably. Using types defined within the System.Threading namespace, you are able to spawn additional threads with minimal fuss and bother. Likewise, when it is time to lock down shared points of data, you will find additional types that provide the same functionality as the Win32 API threading primitives (using a much cleaner object model).
However, the System.Threading namespace is not the only way to build multithread .NET programs. During our examination of the .NET delegate (see Chapter 8), it was mentioned that all delegates have the ability to invoke members asynchronously. This is a major benefit of the .NET platform, given that one of the most common reasons a developer creates threads is for the purpose of invoking methods in a nonblocking (aka asynchronous) manner. Although you could make use of the System.Threading namespace to achieve a similar result, delegates make the whole process much easier.
A Brief Review of the .NET Delegate
Recall that the .NET delegate type is a type-safe object-oriented function pointer. When you declare a .NET delegate, the C# compiler responds by building a sealed class that derives from System.MulticastDelegate (which in turn derives from System.Delegate). These base classes provide every delegate with the ability to maintain a list of method addresses, all of which may be invoked at a later time. Consider the BinaryOp delegate first defined in Chapter 8:
// A C# delegate type.
public delegate int BinaryOp(int x, int y);
Based on its definition, BinaryOp can point to any method taking two integers as arguments and returning an integer. Once compiled, the defining assembly now contains a full-blown class definition that is dynamically generated based on the delegate declaration. In the case of BinaryOp, this class looks more or less like the following (shown in pseudo-code):
sealed class BinaryOp : System.MulticastDelegate
{
public BinaryOp(object target, uint functionAddress); public void Invoke(int x, int y);
public IAsyncResult BeginInvoke(int x, int y, AsyncCallback cb, object state);
public int EndInvoke(IAsyncResult result);
}
Recall that the generated Invoke() method is used to invoke the methods maintained by a delegate object in a synchronous manner. Therefore, the calling thread (such as the primary thread of the application) is forced to wait until the delegate invocation completes. Also recall that in C#, the Invoke() method is not directly called in code, but is triggered under the hood when applying “normal” method invocation syntax. Consider the following program, which invokes the static Add() method in a synchronous (aka blocking) manner:
// Need this for the Thread.Sleep() call. using System.Threading;
using System;
namespace SyncDelegate
{
public delegate int BinaryOp(int x, int y);


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 |
453 |
When you run this program, you should notice that a five-second delay takes place before you see the Console.WriteLine() logic execute. Although many (if not most) methods may be called synchronously without ill effect, .NET delegates can be instructed to call their methods asynchronously if necessary.
■Source Code The SyncDelegate project is located under the Chapter 14 subdirectory.
The Asynchronous Nature of Delegates
If you are new to the topic of multithreading, you may wonder what exactly an asynchronous method invocation is all about. As you are no doubt fully aware, some programming operations take time. Although the previous Add() was purely illustrative in nature, imagine that you built a single-threaded application that is invoking a method on a remote object, performing a long-running database query, or writing 500 lines of text to an external file. While performing these operations, the application will appear to hang for quite some time. Until the task at hand has been processed, all other aspects of this program (such as menu activation, toolbar clicking, or console output) are unresponsive.
The question therefore is, how can you tell a delegate to invoke a method on a separate thread of execution to simulate numerous tasks performing “at the same time”? The good news is that every .NET delegate type is automatically equipped with this capability. The even better news is that you are not required to directly dive into the details of the System.Threading namespace to do so (although these entities can quite naturally work hand in hand).
The BeginInvoke() and EndInvoke() Methods
When the C# compiler processes the delegate keyword, the dynamically generated class defines two methods named BeginInvoke() and EndInvoke(). Given our definition of the BinaryOp delegate, these methods are prototyped as so:
sealed class BinaryOp : System.MulticastDelegate
{
...
//Used to invoke a method asynchronously. public IAsyncResult BeginInvoke(int x, int y,
AsyncCallback cb, object state);
//Used to fetch the return value
//of the invoked method.
public int EndInvoke(IAsyncResult result);
}
The first stack of parameters passed into BeginInvoke() will be based on the format of the C# delegate (two integers in the case of BinaryOp). The final two arguments will always be System. AsyncCallback and System.Object. We’ll examine the role of these parameters shortly; for the time being, though, we’ll supply null for each.
The System.IAsyncResult Interface
Also note that the BeginInvoke() method always returns an object implementing the IAsyncResult interface, while EndInvoke() requires an IAsyncResult-compatible type as its sole parameter. The IAsyncResult-compatible object returned from BeginInvoke() is basically a coupling mechanism that allows the calling thread to obtain the result of the asynchronous method invocation at a later time via EndInvoke(). The IAsyncResult interface (defined in the System namespace) is defined as follows: