Click here to Skip to main content
Click here to Skip to main content

Threads, Process, and AppDomains

, 30 May 2009
Rate this:
Please Sign up or sign in to vote.
An article for the beginner to help understand Application Domains

Introduction: How It Works

The .NET Framework runs on top of the Windows operating system and as such, must be built using technologies that Windows can interface with. All managed modules and assembly files must use the PE (Portable Executable) format and must therefore be either a Windows executable or a Windows DLL. Further, the CLR and mscorlib.dll are the two most important components of the .NET Framework. The two of the most powerful resultant features of the .NET Framework are hosting and AppDomains. An Application Domain is a light-weight process that functions as a unit of isolation within that process to further function as a logical container that allows multiple assemblies to run within a single process which prevents them from directly accessing other assemblies’ memories. While considered a light-weight process, AppDomains still offer many of the features that a Windows process offers: separate memory spaces and access to resources. AppDomains are actually more efficient than a process by enabling multiple assemblies to be run in separate application domains without the overhead of launching separate processes.

If you think about it, a Window process is normally written in C/C++. Most of the application DLLs and nearly all of the system DLLs are written in the native C language. But when developing the CLR Microsoft implemented it as a COM server contained inside a DLL: that is, Microsoft defined a standard COM interface for the CLR and assigned GUIDs (the unique identifiers that represent both the physical and the logical identifiers of a COM component) to this interface and the COM server. When you install the .NET Framework, the COM server representing the CLR is registered in the Windows registry just like any other COM component. The MSCorEE.h C++ header file ships with the .NET Framework SDK. This header file defines the GUIDs and the unmanaged ICLRRuntimeHost interface definition. Any Windows application can host the CLR but you don't instantiate the CLR COM server by calling CoCreateInstance; instead your unmanaged host should call the CorBindToRuntimeEx function, or another similar function, all of which are declared in the MSCorEE.h header file. The CorBindToRuntimeEx function is implemented in the MsCorEE.dll file that resides in the %systemroot%/system32 directory. This DLL then, is not a managed assembly like mscorlib.dll, but rather is called a shim. Its job is to determine which version of the CLR to create: either one for a server or one for a stand-alone desktop. The shim DLL does not contain the CLR COM server itself.

When the CorBindToRuntimeEx function is called, its arguments allow the host to specify which version of the CLR it would like to create, usually .NET 2.0 version 2.0.50727, as well as some other settings. Because the CorBindToRuntimeEx function gathers information about the number of CPUs installed on the machine, it can decide which version of the CLR to load -- the shim might not load the version that the host requested. This is why the CorBindToRuntimeEx function gathers this additional information. By default, when a managed executable starts, the shim examines the executable file and extracts the information indicating the version of the CLR that the application was built and tested with. The CorBindToRuntimeEx function returns a pointer to the unmanaged ICLRRuntimeHost interface. The hosting application can call methods defined by this interface to:

  • Set Host Managers: Tell the CLR that the host wants to be involved in making decisions related to memory allocations, thread scheduling/synchronization, assembly loading, etc.
  • Get Host Managers: Tell the CLR to prevent the use of some classes/members
  • Initialize and start the CLR
  • Load an assembly and execute code in it
  • Stop the CLR, thus preventing any managed code from running in the Windows process.

When the CLR COM server initializes, it creates an AppDomain, which then functions as that logical container for a set of assemblies. The first AppDomain is created when the CLR initialized is called the default AppDomain; this AppDomain is destroyed only when the Windows process terminates. But in addition to the default AppDomain, a host using either unmanaged COM interface methods or managed type methods can instruct the CLR to create additional AppDomains. Note that the entire purpose of the AppDomain is to provide isolation. Application domains are implemented in the .NET Framework using the System.AppDomain class. To use an application domain, you create an instance of the AppDomain class, and then execute an assembly within that domain:

using System;
class Test {
static void Main() {
AppDomain d = AppDomain.CreateDomain("NewDomain");
Console.WriteLine("Host domain:  "  + AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("Child domain:  "   + d.FriendlyName);
  }
}

Output:

c:\Windows\MICROS~1.NET\FRAMEW~1\V20~1.507>ADomain.exe
Host domain:  ADomain.exe
Child domain:  NewDomain

Here is an example of constructing an assembly name:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;

public class Test {
public static void Main() {
 AssemblyName mscorlib = new AssemblyName();
            mscorlib.Name = "mscorlib";
            mscorlib.Version = new Version(2, 0, 0, 0);
            mscorlib.CultureInfo = CultureInfo.InvariantCulture;
            mscorlib.SetPublicKeyToken(new byte[] {
                0xb7, 0x7a, 0x5c, 056, 0x19, 0x34, 0xe0, 0x89 });
            mscorlib.ProcessorArchitecture = ProcessorArchitecture.X86;

            Console.WriteLine(mscorlib);
        }
}

When we compile this code, we get:

C:\Windows\MICROS~1.NET\FRAMEW~1\V20~1.507>AssemblyName.exe


mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c381934e089

And this is a basic look at unloading the assembly by creating an AppDomain:

public static void Main()
        {
            // Set up a new AppDomain, and load the DLL inside of it:
            AppDomain ad = AppDomain.CreateDomain("Isolated");

            //
            // We can then dump the contents of the AppDomain:
            AppDomain.Unload(ad);
        }
}

Now here is a test if System.dll is unloaded:

using System;
using System.Collections.Generic;
public class Test {
public static void Main()
        {
            Console.WriteLine("Before: {0}", Environment.WorkingSet);

            // Loads System.dll (if it's not already loaded):
            Type uriType = typeof(Uri); 
            Console.WriteLine("After loading System.dll: {0}", Environment.WorkingSet);

            // Loads System.dll into 10 new AppDomains:
            List<appdomain /> ads = new List<appdomain />();
            for (int i = 0; i < 10; i++)
            {
                AppDomain ad = AppDomain.CreateDomain(i.ToString());
                ad.DoCallBack(delegate { Type t = typeof(Uri); });
                ads.Add(ad);
            }
            Console.WriteLine("After loading System.dll into 10 AppDomains: 
					{0}", Environment.WorkingSet);
            // Unload the appdomains and check the working set:
            foreach (AppDomain ad in ads)
                AppDomain.Unload(ad);
            Console.WriteLine("After unloading the AppDomains: {0}", 
						Environment.WorkingSet);
        }
}

The output:

Before: 5943296
After loading System.dll: 6107136
After loading System.dll into 10 AppDomains: 9572352
After unloading the AppDomains: 9732096

Processes in the .NET Framework correspond one to one with a process in Windows. A process’s primary purpose is to manage per-program resources; this includes a shared virtual address space among all threads running in that process, a HANDLE table, a shared set of DLLs (mapped into the same address space), and more other process-wide data that is stored in the Process Environment Block (PEB). Problems with one process will normally not affect other processes because of this isolation. But because of inter-process communication and system-wide shared resources – such as files, memory-mapped I/O, and named kernel objects – a process can interfere with another process. A single managed process (one which has loaded the CLR) can contain many AppDomains. It is suggested that the reader read up on and read some articles on the Thread Pool and the APM Programming Model. These topics involve a pool of threads in which there is one thread pool per process, and the ability to execute code asynchronously during a computation process. Below is some sample to exemplify getting information about a thread pool:

using System;
using System.Threading;
public class Test {
public static void Main()
        {
            // Retrieve the info:
            int minWorkerThreads, maxWorkerThreads, availableWorkerThreads;
            int minIoThreads, maxIoThreads, availableIoThreads;
            ThreadPool.GetMinThreads(out minWorkerThreads, out minIoThreads);
            ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIoThreads);
            ThreadPool.GetAvailableThreads(
                out availableWorkerThreads, out availableIoThreads);

            // Now print it out:
            Console.WriteLine("Min/max/available worker threads: {0}/{1}/{2}",
                minWorkerThreads, maxWorkerThreads, availableWorkerThreads);
            Console.WriteLine("Min/max/available IO threads: {0}/{1}/{2}",
                minIoThreads, maxIoThreads, availableIoThreads);
        }
}

Compile and run this code to get information about the CLR's internal code that manages the thread pool. Now is a final example of getting all of the active processes listed while an instance of an unmanaged host, Internet Explorer, is running:

using System;
using System.Diagnostics;
using System.Threading;
public class Test {
 public static void Main()
        {
            Console.WriteLine("Current process:");
            PrintProcess(Process.GetCurrentProcess());

            Console.WriteLine("IE processes:");
            Process[] iexplorerProcs = Process.GetProcessesByName("iexplore");
            foreach (Process p in iexplorerProcs) PrintProcess(p);

            Console.WriteLine("All active processes:");
            Process[] allProcs = Process.GetProcesses();
            foreach (Process p in allProcs) PrintProcess(p);
        }
  private static void PrintProcess(Process p)
        {
            Console.WriteLine("  -> {0} - {1}", p.ProcessName, p.PeakWorkingSet64);
        }
 }

Examine the output:

Current process:
  -> Procc - 6279168
IE processes:
  -> iexplore - 70152192
All active processes:
  -> GoogleToolbarUser - 6385664
  -> WINWORD - 54874112
  -> GoogleToolbarNotifier - 8638464
  -> svchost - 73224192
  -> winlogon - 6696960
  -> spoolsv - 10362880
  -> svchost - 12292096
  -> svchost - 2220032
  -> cmd - 2166784
  -> audiodg - 15163392
  -> hpqste08 - 16248832
  -> svchost - 9318400
  -> notepad - 5001216
  -> svchost - 6324224
  -> svchost - 3137536
  -> SearchIndexer - 23379968
  -> csrss - 13602816
  -> hpwuSchd2 - 2953216
  -> WZQKPICK - 4448256
  -> taskeng - 5509120
  -> Procc - 6856704
  -> hpqtra08 - 11644928
  -> wininit - 4194304
  -> svchost - 30564352
  -> svchost - 3657728
  -> unsecapp - 4689920
  -> svchost - 99524608
  -> svchost - 7344128
  -> notepad - 5156864
  -> svchost - 6287360
  -> cmd - 2871296
  -> taskeng - 9412608
  -> explorer - 71344128
  -> svchost - 11722752
  -> lsm - 3989504
  -> SLsvc - 14442496
  -> MSASCui - 9420800
  -> ieuser - 8790016
  -> dwm - 4603904
  -> lsass - 8232960
  -> svchost - 111050752
  -> notepad - 5521408
  -> svchost - 3964928
  -> FlashUtil10b - 4689920
  -> services - 7118848
  -> WmiPrvSE - 5750784
  -> iexplore - 70152192
  -> smss - 778240
  -> svchost - 33824768
  -> csrss - 5222400
  -> System - 10354688
  -> svchost - 5210112
  -> ntvdm - 4370432
  -> Idle - 0

References

  • The CLR via C#, 2nd Edition, by Jeffrey Richter

History

  • 4th April, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

logicchild
Other Pref. Trust
United States United States
I started electronics training at age 33. I began studying microprocessor technology in an RF communications oriented program. I am 43 years old now. I have studied C code, opcode (mainly x86 and AT+T) for around 3 years in order to learn how to recognize viral code and the use of procedural languages. I am currently learning C# and the other virtual runtime system languages. I guess I started with the egg rather than the chicken. My past work would indicate that my primary strength is in applied mathematics.

Comments and Discussions

 
Generali'm getting an error? [modified] PinmemberYankee Imperialist Dog!28-May-09 3:29 
GeneralRe: i'm getting an error? Pinmemberlogicchild30-May-09 12:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 30 May 2009
Article Copyright 2009 by logicchild
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid