Application domains (or simply AppDomains) are logical subdivisions within a given process that host a set of related .NET Core assemblies. An AppDomain is further subdivided into contextual boundaries which are used to group like-minded .NET Core objects.

It is important to understand processes, AppDomains, and object contexts when working with numerous .NET Core APIs including multithreading, parallel processing, and object serialization.

The Role of a Windows Process


A process is a running program, in simple terms. Formally speaking, a process is an operating system-level concept used to describe a set of resources and the necessary memory allocations used by a running application. For each .NET Core application loaded into memory, the OS creates a separate and isolated process for use during its lifetime. The process can be regarded as a fixed, safe boundary for a running application.

The Role of Threads


Every Windows process contains an initial "thread" that functions as the entry point for the application. A thread is a path of execution within a process. The first thread created by a process's entry point is termed the primary thread. The primary thread is created when the Main() method (or file with top level statements) is invoked.

Processes that contain a single primary thread of execution are intrinsically thread-safe. A single-thread could have the drawback of seeming unresponsive to the user if it were performing a complex operation.

The operating systems supported by .NET Core make it possible for the primary thread to spawn additional secondary threads, which are called worker threads, using a handful of API functions such as CreateThread(). Each thread becomes a unique path of execution in the process and has concurrent access to all shared points of data within the process.

Developers typically create additional threads to improve the program's overall responsiveness. But, using too many threads in a single process can potentially degrade performance, due to the CPU siwtching between the active threads which takes time.

On some machines, multithreading is most commonly an illusion provided by the OS. Machines that host a single, nonhyperthreaded CPU do not have the ability to literally handle multiple threads. The CPU will execute a thread for a unit of time called a time slice based in part on the thread's priority level. When the thread's time slice is up, the existing thread is suspended so that another thread can perform its business. To remember what was happening before it was kicked out of the way, each thread is given the ability to write to Thread Local Storage (TLS) and is provided with a separate call stack.



If you don't want to sweat the details, just remember that a thread is a unique path of execution within a Windows process. Every process has a primary thread and may contain additional threads that have been programmatically created.

Interacting with Processes Using .NET Core


The System.Diagnostics namespace defines several types that allow you to programmatically interact with processes and various diagnostic-related types such as the system event log and performance counters.



The System.Diagnostics.Process class allows you to analyze the processes running on a given machine (local or remote). Process also provides members to programmatically start and terminate processes, change the priority levels of threads, and obtain a list of active threads.



Process also defines a few useful methods:

Enumerating Running Processes


static void ListAllRunningProcesses()
{
  // Get all processes on the local machine, ordered by PID
  var runningProcs =
    from proc
    in Process.GetProcesses(".")
    orderby proc.Id
    select proc;

  // Print out PID and name of each process
  foreach(var p in runningProcs)
  {
    string info = $"-> PID: {p.Id}\tName: {p.ProcessName}";
    Console.WriteLine(info);
  }
}

Process.GetProcesses() returns an array of Process objects that represent the running processes on the target machine. The dot notation here represents the local computer.

Investigating a Specific Process


You can get a single process by its associated PID using Process.GetProcessById().

Investigating a Process's Thread Set


The set of threads is represented by the strongly typed ProcessThreadCollection collection, which contains some number of ProcessThread objects.

theProc = Process.GetProcessById(pID);
ProcessThreadCollection theThreads = theProc.Threads;

foreach(ProcessThread pt in theThreads)
{
  ...
}



Note that the ProcessThread type is not used to create, suspend, or kill threads in .NET. It is just for getting diagnostic information.

Investigating a Process's Module Set


When talking about processes, a module is a general term used to describe a given *.dll (or the *.exe itself) that is hosted by a specific process. When you access the ProcessModuleCollection via the Process.Modules property, you can enumerate over all modules hosted within a process which can include traditional C-based libraries in addition to .NET Core-based.

Starting and Stopping Processes Programmatically


The Start() and Kill() methods of the System.Diagnostics.Process class provide a way to programatically launch and terminate a process.

proc = Process.Start(@"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", "www.facebook.com");

foreach (var p in Process.GetProcessesByName("MsEdge"))
{
  p.Kill(true);
}

The static Process.Start() method is overloaded, and at minimum, you need to specify the path and the filename of the process you want to launch. It returns a reference to the newly activated process which can be killed using the instance-level method Kill().

Controlling Process Startup Using the ProcessStartInfo Class


Process.Start() allows you to pass in a System.Diagnostics.ProcessStartInfo type that specifies additional information regarding how a process should come to life.

Leveraging OS Verbs with ProcessStartInfo


ProcessStartInfo can be used to take advantage of file associations.

Understanding .NET Application Domains


Under the .NET and .NET Core platforms, executables are not hosted directly within a Windows process. Rather, .NET and .NET Core executables are hosted by a logical partition within a process called an application domain.

AppDomains are a key aspect of the OS-neutral nature of the .NET Core platform. This logical division abstracts away the differences in how the underlying OS represents the loaded executable.

AppDomains are also less expensive in terms of CPU and memory than a full-blown process.

AppDomains are fully and completely isolated from other AppDomains within a process. An application running in one AppDomain is unable to obtain data of any kind from another AppDomain, unless they use a distributed programming protocol.

In .NET Core, there is exactly one AppDomain. The ApplicationLoadContext provides assembly isolation in .NET Core.

The System.AppDomain Class


The AppDomain is largely deprecated with .NET Core.

Interacting with the Default Application Domain


Your application has access to the default application domain using the static AppDomain.CurrentDomain property.

Enumerating Loaded Assemblies


AppDomain defaultAD = AppDomain.CurrentDomain;

Assembly[] loadedAssemblies = defaultAD.GetAssemblies();

Assembly Isolation with Application Load Contexts


AppDomains are logical partitions used to host .NET Core assemblies. An application domain can be further subdivided into numerous load context boundaries. Conceptually, a load context creates a scope for loading, resolving, and potentially unloading a set of assemblies. In a nutshell, a .NET Core load context provides a way for a single AppDomain to establish a "specific home" for a given object.

Article notes

What can be regarded as a fixed, safe boundary for a running application?
What are logical subdivisions within a given .NET process?
In really simple terms, what is a process?
What is uniquely assigned to every Windows process?
What formally speaking is an operating system-level concept that is a set of resources and the necessary allocations used by a running application?
On machines with a single (nonhyperthreaded) CPU that cannot literally handle more than one thread at a time, the single CPU executes one thread for a unit of time based in part on the thread's priority level, and then suspend that thread to allow another thread to do some work. The unit of time is called a what?
What namespace and class in .NET allows you to analyze the processes on a given machine (local or remote) and programmatically start and stop processes?
What namespace and class in .NET represents a thread within a given process (it is used to diagnose a process's thread set, and is not used to spawn new threads in a process)?
What type provides a strongly typed collection of ProcessThread objects in the System.Diagnostics namespace?
What property of the System.Diagnostics.Process type provides access to the ProcessThreadCollection class?
What property of the ProcessThread type gets the current priority of the thread?
What property of the ProcessThread type sets the preferred processor for the thread to run on?
What property of the ProcessThread type is used to get or set the priority level of the thread?
What property of the ProcessThread type gets the memory address of the function that the operating system called that started the thread?
.NET executables are hosted by a logical partition within a process called a what?
What key aspect of the OS-neutral nature of .NET abstracts the differences in how an underlying OS represents a loaded executable?
What are far less expensive in terms of processing power and memory than full-blown processes and thus allow the CoreCLR to load and unload things faster than a formal process and improve server app scalability?
A .NET process application domain is capable of hosting and executing any number of related what (answer is not contextual boundaries)?
How many AppDomains can be in a process (as of .NET Core)?
What is a general term used to describe a given *.dll (or the *.exe itself) that is hosted by a specific process?
What type is a vehicle to obtain diagnostic information for the active Windows threads within a running process (and not used to create multithreaded apps)?
What static method of System.Diagnostics.Process returns a new Process object that represents the currently active process?
What property of System.Diagnostics.Process gets the PID of the associated process?
What property of System.Diagnostics.Process gets the name of the computer that the associated process is running on?
What property of System.Diagnostics.Process gets the name of the process ("which, as you would assume, is the name of the application itself")?
What property of System.Diagnostics.Process gets a value indicating whether the user interface of the process is responding to user input (or is currently "hung")?
What property of System.Diagnostics.Process returns a collection of ProcessThread objects?
What static method of System.Diagnostics.Process returns an array of new Process objects running on a given machine?
What method of System.Diagnostics.Process closes a process that has a user interface by sending a close message to its main window?
What method of System.Diagnostics.Process immediately stops the associated process?
What is a path of execution within a process?
What is the keystroke combo to active the Window Task Manager utility?
How are application domains further divided (.NET)?
What is used to group like-minded .NET objects inside of an AppDomain?
What is a logical subdivision within a given process that hosts a set of related .NET assemblies (in a nutshell)?
A .NET AppDomain is further subdivided into what (which are used to group like-minded .NET objects)?
What term refers to the initial thread of a process, that would be created when the Main() method of a C# project was invoked?
A process that contains only a single primary thread intrinsically is what?
When a process spawns additional secondary threads, what are those threads called?
What always has a primary thread, and may contain additional threads that have been programmatically created?
Previous Next