The domain of the thread

I remember that the first time I heard it, it seemed strange that the same thread could have parts of its stack running in different Appdomains. Since it’s possible to unload an AppDomain, the CLR needs to have a way to abort those parts of the stack that will return to functions that are in any AppDomain that is going to be unloaded. It does this by raising ThreadAbort to give those stack frames a chance to exit, and then throws AppDomainUnload to the function that calls into the unloaded AppDomain in the first place.

The situation is easy to set up. We create a number of domains and load an object into each of them. We then call a method that transitions into the next AppDomain, and when we have transitioned into them all, we can call the AppDomain.Unload.

namespace ConsoleApplication1
{
    public class Client : MarshalByRefObject
    {
        public void TransitionIntoDomain(Queue<AppDomain> domains, AppDomain domainToKill)
        {
            PrintThreadDetails();
            try
            {
                if (domains.Count > 0)
                {
                    AppDomain nextDomain = domains.Dequeue();
                    Client client = (Client)nextDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, "ConsoleApplication1.Client");
                    client.TransitionIntoDomain(domains, domainToKill);
                    Console.WriteLine("Call returned to domain {0}", AppDomain.CurrentDomain.FriendlyName);
                }
                else
                {
                    AppDomain.Unload(domainToKill);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Caught exception {0} in domain {1}",  ex.GetType().Name,  AppDomain.CurrentDomain.FriendlyName);
            }
        }

        public void PrintThreadDetails()
        {
            Console.WriteLine("Running in domain {0} on thread {1}", AppDomain.CurrentDomain.FriendlyName, Thread.CurrentThread.ManagedThreadId);
        }
    }

    class Program
    {
        static void Main()
        {
            Client client = new Client();
            Queue<AppDomain> domains = new Queue<AppDomain>();
            foreach (string name in new string[] { "domain1", "domain2", "domain3", "domain4", "domain5" })
            {
                domains.Enqueue(AppDomain.CreateDomain(name));
            }
           client.TransitionIntoDomain(domains, domains.Where(x => x.FriendlyName == "domain3").First());
        }
    }
}

We can see the exceptions that get thrown from the output of the program.

Running in domain ConsoleApplication3.exe on thread 1
Running in domain domain1 on thread 1
Running in domain domain2 on thread 1
Running in domain domain3 on thread 1
Running in domain domain4 on thread 1
Running in domain domain5 on thread 1
Caught exception ThreadAbortException in domain domain5
Caught exception ThreadAbortException in domain domain4
Caught exception ThreadAbortException in domain domain3
Caught exception AppDomainUnloadedException in domain domain2
Call returned to domain domain1
Call returned to domain ConsoleApplication3.exe

An interesting follow on experiment is to try to stop the ThreadAbortException by catching it and using Thread.ResetAbort(), and another experiment is to have the same domain multiple times on the queue.

In the first case we add the following code at the start of the catch clauses for the try:

catch (ThreadAbortException ex)
{
    Console.WriteLine("Abort caught");
    Thread.ResetAbort();
}

This gives

Running in domain ConsoleApplication3.exe on thread 1
Running in domain domain1 on thread 1
Running in domain domain2 on thread 1
Running in domain domain3 on thread 1
Running in domain domain4 on thread 1
Running in domain domain5 on thread 1
Abort caught
Abort caught
Abort caught
Caught exception AppDomainUnloadedException in domain domain2
Call returned to domain domain1
Call returned to domain ConsoleApplication3.exe

For the case where we enter the same domain multiple times, we get output such as the following:

Running in domain ConsoleApplication3.exe on thread 1
Running in domain domain1 on thread 1
Running in domain domain2 on thread 1
Running in domain domain3 on thread 1
Running in domain domain4 on thread 1
Running in domain domain5 on thread 1
Running in domain domain1 on thread 1
Running in domain domain2 on thread 1
Running in domain domain3 on thread 1
Running in domain domain4 on thread 1
Running in domain domain5 on thread 1
Caught exception ThreadAbortException in domain domain5
Caught exception ThreadAbortException in domain domain4
Caught exception ThreadAbortException in domain domain3
Caught exception ThreadAbortException in domain domain2
Caught exception ThreadAbortException in domain domain1
Caught exception ThreadAbortException in domain domain5
Caught exception ThreadAbortException in domain domain4
Caught exception ThreadAbortException in domain domain3
Caught exception AppDomainUnloadedException in domain domain2
Call returned to domain domain1
Call returned to domain ConsoleApplication3.exe

One final thing to do is to unload the AppDomain that is running the call to Unload. I expect you can guess what happens in that case.

Advertisements
This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s