Working with AppDomains

An application domain (AppDomain) is a kind of virtual application. It contains and runs code, starts multiple threads, and links to any needed reference, such as external assemblies or COM libraries.

Application domains can be created to isolate portions of an application and prevent them from directly contacting other portions; to configure different kinds of security authorizations, such as with Code Access Security (CAS) techniques to limit I/O access, network access, and so on; or to simply increment the whole security level of the application by isolating different application contexts from others.

Working with AppDomains

AppDomain usage for reference and/or CAS isolation

The application domains can be unloaded if needed, allowing us to work in a reliable way with multiple external plugins or extensions, like the ones from IoC designs, or simply because we need to load multiple versions of the same assembly all together.

Working with AppDomains

External libs loaded at runtime in temporary AppDomains

Application domains also give us the ability to start multiple applications within a single Windows process that usually consumes several resources in multiple logical applications that are exposed as different application domains, each isolated by others as if they're different processes. The Windows Communication Foundation (WCF) handles its clients in a design similar to the following diagram:

Working with AppDomains

Different assembly rules exposed as different DLL files, all loaded as different virtual processes in the same physical process as AppDomains

Keep in mind that the main goal of using an AppDomain class is always isolation. Thus, often some resource usage is incremented. An example is that when loading the same assembly in multiple AppDomains, it will produce multiple instances of the Type class for each type that is contained in the referred assembly by each AppDomain. Although the JIT is made only once, the compiled IL is copied across the multiple type-and-metadata tables that live within each AppDomain. The only objection is for the neutral AppDomain, which is a kind of shared AppDomain across all the processes; it does not waste resources and cannot be unloaded by invoking the AppDomain.Unload method.

The creation of an AppDomain class is straightforward, as shown in the following code snippet:

var d = AppDomain.CreateDomain("AppDomain1");
d.ExecuteAssembly("ConsoleApplication2.exe");
AppDomain.Unload(d);

The Load method of the AppDomain class always loads an assembly in the current application domain. Thus, the best way to load assemblies within a defined AppDomain class is within its code using the Assembly.Load method or by calling ExecuteAssembly method, as shown in the preceding example.

The CreateDomain method actually returns a proxy to the real objects, giving the other application domains the ability to invoke some method on the remote one (AppDomain1). Those proxies are part of .NET Remoting, a distributed programming framework derived from CORBA. Currently, WCF TcpBinding is compatible with Remoting, although it is heavily evolved and optimized to fulfill SOA requirements.

When multiple objects live in multiple AppDomain, some communication may occur between those domains. Other than the option of using an external component, such as a file or service such as any WCF binding, any instance in any AppDomain can produce a Remoting proxy to invoke distance methods. Such instances will be marshaled (copied between process boundaries) by value, serializing the object itself, or by reference, with a specific class heritage. Here's an example of this:

var domain1 = AppDomain.CreateDomain("domain1");
var domain2 = AppDomain.CreateDomain("domain2");

var byValueType=typeof(MarshalledByValueClass);
var byValue = (MarshalledByValueClass)domain1.CreateInstanceAndUnwrap(byValueType.Assembly.GetName().FullName, byValueType.FullName);

var byReferenceType=typeof(MarhalledByReferenceClass);
var byReference = (MarhalledByReferenceClass)domain2.CreateInstanceAndUnwrap(byReferenceType.Assembly.GetName().FullName, byReferenceType.FullName);

Console.WriteLine("MarshalledByValueClass -> domain: {0}	isProxy: {1}", byValue.DomainName, RemotingServices.IsTransparentProxy(byValue));
Console.WriteLine("MarhalledByReferenceClass -> domain: {0}	isProxy: {1}", byReference.DomainName, RemotingServices.IsTransparentProxy(byReference));

AppDomain.Unload(domain1);
AppDomain.Unload(domain2);
Console.ReadLine();

These new classes are available, although from another assembly:

[Serializable]
public sealed class MarshalledByValueClass
{
    public string DomainName { get; set; }
    public MarshalledByValueClass()
    {
        DomainName = AppDomain.CurrentDomain.FriendlyName;
    }
}

public sealed class MarhalledByReferenceClass : MarshalByRefObject
{
    public string DomainName { get; set; }
    public MarhalledByReferenceClass()
    {
        DomainName = AppDomain.CurrentDomain.FriendlyName;
    }
}

In this example, two different objects cross the AppDomain execution. The first object (named byValueType) used the by-value marshaling by being decorated with the SerializableAttribute class. This means that when the object crossed boundaries of two AppDomains, it got serialized/de-serialized each time. Bear in mind that the caller is within the default AppDomain class.

The second object (named byReferenceType), instead, used the by-reference marshaling by inheriting the MarshalByRefObject class. Actually, such an object never crosses the boundaries of the two AppDomains. A remote proxy is available to remote the AppDomains classes to invoke remote methods and read/write remote properties.

The first difference is that for the marshaled-by-value instance, only the constructor actually works in the other domain. Thus, after it is unloaded, the object copy is still alive in the calling domain, ready to do anything else. For the marshaled-by-reference instance, the object actually lives in the remote domain; hence, any proxy usage after the domain unloads will raise an AppDomainUnloadedException event, proving that a single instance of such an object exists.

This is the execution console output:

MarshalledByValueClass -> domain: domain1       isProxy: False
MarhalledByReferenceClass -> domain: domain2    isProxy: True

IDisposable interface

The IDisposable interface, when implemented in any class, informs CLR that such an object will handle some external resource or unmanaged handle. The best behavior here is to free up or disconnect from such costly resource as soon as possible, although the object collection will occur later with GC logics.

Once such an interface is implemented, the usage must tell CLR that cleanups of such resources must occur at a specific time, creating a local scope with usual parenthesis in C# or a specific End instruction in VB, by using the using keyword:

class Program
{
    static void Main(string[] args)
    {
        using (var instance = new ExternalResourceContainer())
        {

        } //here automatically CLR will invoke .Dispose method
    }
}

public class ExternalResourceContainer : IDisposable
{
    private object externalResource;
    public void Dispose()
    {
        //release resource usage
    }
}

We may also invoke the Dispose method manually if we cannot use the using block.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.107.229