FREE BOOK

Chapter 10: Processes, AppDomains,Contexts, and Threads

Posted by Apress Free Book | C#.NET February 02, 2009
In this Chapter you drill deeper into the constitution of a .NET executable host and come to understand the relationship between Win32 processes, application domains, contexts, and threads.

Fun with Context

To begin, assume you have a new console application named ContextManipulator. This application defines two context-agile types and a single context-bound type:

using System.Runtime.Remoting.Contexts; // For Context type.
using System.Threading; // For Thread type.

    // These classes have no special contextual
    // needs and will be loaded into the
    // default context of the app domain.
    public class NoSpecialContextClass
    {
        public NoSpecialContextClass()
        {
            // Get context information and print out context ID.
            Context ctx = Thread.CurrentContext;
            Console.WriteLine("Info about context {0}", ctx.ContextID);
            foreach (IContextProperty itfCtxProp in ctx.ContextProperties)
                Console.WriteLine("-> Ctx Prop: {0}", itfCtxProp.Name);
        }
    }
    public class NoSpecialContextClass2
    {
        public NoSpecialContextClass2()
        {
            // Get context information and print out context ID.
            Context ctx = Thread.CurrentContext;
            Console.WriteLine("Info about context {0}", ctx.ContextID);
            foreach (IContextProperty itfCtxProp in ctx.ContextProperties)
                Console.WriteLine("-> Ctx Prop: {0}", itfCtxProp.Name);
        }
    }
    // This class demands to be loaded in
    // a synchronization context.
    // RECALL! All context-bound types must derive from
    // System.ContextBoundObject.
    [Synchronization]
    public class SynchContextClass : ContextBoundObject
    {
        public SynchContextClass()
        {
            // Get context information and print out context ID.
            Context ctx = Thread.CurrentContext;
            Console.WriteLine("Info about context {0}", ctx.ContextID);
            foreach (IContextProperty itfCtxProp in ctx.ContextProperties)
                Console.WriteLine("-> Ctx Prop: {0}", itfCtxProp.Name);
        }
    }

Notice that each of the class constructors obtains a System.Runtime.Remoting.Contexts.Context type from the current thread of execution, via the static Thread.CurrentContext property. Using this type, you are able to print out statistics about the contextual boundary, such as its assigned ID, as well as a set of descriptors obtained via the Context.ContextProperties property.

This instance-level property returns a (very) low-level interface named IContextProperty, which exposes each descriptor through the Name property.

Now, assume Main() has been updated to allocate an instance of each class type. As the objects come to life, the class constructors will dump out various bits of contextcentric information (Figure 10-10):

       static void Main(string[] args)
        {
            Console.WriteLine("***** The Amazing Context Application *****\n");
            // Make each class type and print contextual info.
            NoSpecialContextClass noBigDealObj = new NoSpecialContextClass();
            Console.WriteLine();
            NoSpecialContextClass2 noBigDealObj2 = new NoSpecialContextClass2();
            Console.WriteLine();
            SynchContextClass synchObj = new SynchContextClass();
            Console.WriteLine();
        }



Figure 10-10. Investigating an object's context

Given that the NoSpecialContextClass and NoSpecialContextClass2 types have not been qualified with a specific context attribute, the CLR will have both loaded into context 0 (i.e., the default context). However, the SynchContextClass type is loaded into a unique contextual boundary within the current application domain, given the fact that this context-bound type was adorned with the [Synchronization] attribute.

SOURCE CODE The ContextManipulator project is included under the Chapter 10 subdirectory.

Summarizing Processes, AppDomains, and Context

So then, at this point in the game you have seen how the .NET runtime has altered the composition of a traditional Win32 process to function within the common language runtime (CLR). To summarize the key points, remember the following:

  • 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 group special needs objects into a logical container, to ensure that their 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. However, this background discussion has provided a solid foundation regarding how the CLR creates, processes, and destroys specific threads of execution (as well as increased your understanding of some underlying CLR concepts).

The Process/AppDomain/Context/Thread Relationship

As mentioned at the opening of this chapter, a thread is a path of execution within a loaded application. While many .NET applications can live happy and productive single-threaded lives, the primary thread may spawn secondary threads of execution to perform additional units of work. In just a moment, you will be introduced to the System.Threading namespace, which defines numerous types used to create multithreaded .NET applications. First however, we need to check out exactly "where" a thread lives within a .NET process.

The first thing you must understand is that under the .NET platform, there is not a direct one-to-one correlation between application domains and threads. In fact, a given AppDomain can have numerous threads executing within it at any given time. Furthermore, a particular thread is not confined to a single application domain during its lifetime. Threads are free to cross application domain boundaries as the thread scheduler and the CLR see fit.

Although active threads can be moved between application boundaries, a given thread can only execute within 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). If you wish to gain access to the AppDomain that is currently hosting the Thread type, call the static Thread.GetDomain method:

            // Thread.GetDomain() returns an AppDomain type.
            Console.WriteLine(Thread.GetDomain().FriendlyName);

A single thread may also be moved into a particular context at any given time, and may be relocated within a new context at the whim of the CLR. If you wish to programmatically discover the current context a thread happens to be executing in, make use of the static Thread.CurrentContext property:

           // CurrentContext returns a Context type.
            Console.WriteLine(Thread.CurrentContext.ContextID);

As you would guess, 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 are able to 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 underlying model.

The Problem of Concurrency and 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 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 and which operations are atomic. (Thread-volatile operations are the dangerous ones!) For example, assume a given thread is accessing a shared point of data (or type member), and begins to modify its contents. Now assume that this thread is instructed to suspend its activity (by the thread scheduler) to allow another thread to access the same point of data.

If the original thread was not completely finished with its current modification of the type, the second incoming thread may be viewing a partially modified object. At this point, the second thread is basically reading bogus values, which is sure to give way to extremely odd (and very hard to find) bugs (which are even harder to replicate and thus debug).

Atomic operations, on the other hand, are always safe in a multithreaded environment, and yet there are very few operations in .NET that are guaranteed to be atomic. Even a simple assignment statement to a double is not atomic! Unless the .NET Framework documentation specifically says an operation is atomic, you must assume it is threadvolatile and take precautions.

Given this, 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, the .NET developer must make use of 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 raw Win32 threading primitives (using a much cleaner object model).

Total Pages : 13 678910

comments