Home > General > Executing code in a new application domain

Executing code in a new application domain

A coworker recently mentioned a scenario where he needs to use legacy code in a multi-threaded environment. The class in-question is written in such a way that it holds state and needs to be reset before the class itself can be used again, therefore preventing it from being used in multiple threads at the same time. Although it may not be the most efficient method, we can create a new application domain for each thread and execute the code in there.

Just prototyping out some ideas, an API like this may be useful:

NewAppDomain.Execute(() =>
{
	// Run code in here
});

Each call to Execute would create a new application domain.

AppDomain domain = AppDomain.CreateDomain("New App Domain: " + Guid.NewGuid());

To execute our Action that we’re passing to the Execute method, we’ll need to create a new instance of Action in our new application domain. However, trying to call the CreateInstanceAndUnwrap method on the new application domain will throw an exception:

Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Action' not found.

Instead, we’ll need to create a new class that can be instantiated in the new application domain. This new class AppDomainDelegate will act as a delgate and execute the Action that we pass to it.

public static class NewAppDomain
{
    public static void Execute(Action action)
    {
        AppDomain domain = null;

        try
        {
            domain = AppDomain.CreateDomain("New App Domain: " + Guid.NewGuid());

            var domainDelegate = (AppDomainDelegate)domain.CreateInstanceAndUnwrap(
                typeof(AppDomainDelegate).Assembly.FullName,
                typeof(AppDomainDelegate).FullName);

            domainDelegate.Execute(action);
        }
        finally
        {
            if (domain != null)
                AppDomain.Unload(domain);
        }
    }

    private class AppDomainDelegate : MarshalByRefObject
    {
        public void Execute(Action action)
        {
            action();
        }
    }
}

Now we can use this class to execute code in another application domain.

NewAppDomain.Execute(() =>
{
    Console.WriteLine("Hello World");
});

Parameters

Trying to use variables declared outside the scope of the lambda will throw a serialization exception.

int i = 0;

NewAppDomain.Execute(() =>
{
	Console.WriteLine("Hello World " + i);
});

We can update the code to receive parameters across application domains by adding an overload for the Execute method:

public static class NewAppDomain
{
    public static void Execute(Action action) { ... }

    public static void Execute<T>(T parameter, Action<T> action)
    {
        AppDomain domain = null;

        try
        {
            domain = AppDomain.CreateDomain("New App Domain: " + Guid.NewGuid());

            var domainDelegate = (AppDomainDelegate)domain.CreateInstanceAndUnwrap(
                typeof(AppDomainDelegate).Assembly.FullName,
                typeof(AppDomainDelegate).FullName);

            domainDelegate.Execute(parameter, action);
        }
        finally
        {
            if (domain != null)
                AppDomain.Unload(domain);
        }
    }

    private class AppDomainDelegate : MarshalByRefObject
    {
        public void Execute(Action action) { ... }

        public void Execute<T>(T parameter, Action<T> action)
        {
            action(parameter);
        }
    }
}

Now we can pass parameters to the method. We’ll need to make sure that any parameters we pass are marked as Serializable since they will be serialized and deserialized across application domains.

int i = 0;

NewAppDomain.Execute(i, x =>
{
    Console.WriteLine("Hello World " + x);
});

Returning Results

In cases where we want the Execute method to return results, we can add two additional method overloads that use Func<T> instead of Action:

public static T Execute<T>(Func<T> action)
{
    AppDomain domain = null;

    try
    {
        domain = AppDomain.CreateDomain("New App Domain: " + Guid.NewGuid());

        var domainDelegate = (AppDomainDelegate)domain.CreateInstanceAndUnwrap(
            typeof(AppDomainDelegate).Assembly.FullName,
            typeof(AppDomainDelegate).FullName);

        return domainDelegate.Execute(action);
    }
    finally
    {
        if (domain != null)
            AppDomain.Unload(domain);
    }
}

public static TResult Execute<T, TResult>(T parameter, Func<T, TResult> action)
{
    AppDomain domain = null;

    try
    {
        domain = AppDomain.CreateDomain("New App Domain: " + Guid.NewGuid());

        var domainDelegate = (AppDomainDelegate)domain.CreateInstanceAndUnwrap(
            typeof(AppDomainDelegate).Assembly.FullName,
            typeof(AppDomainDelegate).FullName);

        return domainDelegate.Execute(parameter, action);
    }
    finally
    {
        if (domain != null)
            AppDomain.Unload(domain);
    }
}

… and the same overloads for the delegate class AppDomainDelegate:

public T Execute<T>(Func<T> action)
{
    return action();
}

public TResult Execute<T, TResult>(T parameter, Func<T, TResult> action)
{
    return action(parameter);
}

We can now get results back. We’ll need to make sure that any results we return are marked as Serializable.

Result first = NewAppDomain.Execute(() => new Result());
Result second = NewAppDomain.Execute(parameter, x => new Result());

Using The Class

The entire class can be found here. To demonstrate, we’ll create a class that uses a public static field as a counter.

public class SharedClass : MarshalByRefObject
{
    public static int Counter = 1;

    public void Print()
    {
        Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + " - " + Counter);
        Counter++;
    }
}

We’ll call the Print method in another application domain using our NewAppDomain class:

NewAppDomain.Execute(() =>
{
	new SharedClass().Print();
	new SharedClass().Print();
});

NewAppDomain.Execute(() =>
{
	new SharedClass().Print();
	new SharedClass().Print();
	new SharedClass().Print();
	new SharedClass().Print();
});

The result:

New App Domain: 3ca2e6de-a5bd-40ef-b471-edb8c19024c2 - 1
New App Domain: 3ca2e6de-a5bd-40ef-b471-edb8c19024c2 - 2
New App Domain: 2037d461-79fe-48c1-abf1-2c58bb6b02cf - 1
New App Domain: 2037d461-79fe-48c1-abf1-2c58bb6b02cf - 2
New App Domain: 2037d461-79fe-48c1-abf1-2c58bb6b02cf - 3
New App Domain: 2037d461-79fe-48c1-abf1-2c58bb6b02cf - 4

If the application domain was the same, we would have seen the counter increment to 6. Notice that SharedClass inherits from MarshalByRefObject. If we instantiate a new instance of SharedClass and pass it as a parameter to our Execute method, this object is now shared across application domains via remoting. To demonstrate, we can use the Execute overload that takes a parameter:

var sharedClass = new SharedClass();

sharedClass.Print();
sharedClass.Print();

NewAppDomain.Execute(sharedClass, x => x.Print());

sharedClass.Print();

The result:

AppDomainDemo.exe - 1
AppDomainDemo.exe - 2
AppDomainDemo.exe - 3
AppDomainDemo.exe - 4

As expected, the counter did not reset. However, if we change SharedClass by removing MarshalByRefObject and marking the class as Serializable, we’ll see a different result:

AppDomainDemo.exe - 1
AppDomainDemo.exe - 2
New App Domain: b9404ee9-f4fc-4937-84ad-9d0369e36bb6 - 1
AppDomainDemo.exe - 3
  1. April 8, 2012 at 9:10 am

    Nice article, app domain creation is basically one of the thermonuclear options in .NET, but this is one of the thousands of reasons .NET is just the bomb. It gives you all of these tools that even if 99% of the time they won’t need to be used that you can get it from your toolbelt when it is needed.

  2. April 8, 2012 at 4:57 pm

    Excellent information. I agree with your comment regarding .net

  3. July 21, 2014 at 2:12 pm

    Good post. I learn something totally new and challenging
    on blogs I stumbleupon everyday. It will always be interesting to read content from
    other authors and use something from other websites.

  4. July 31, 2014 at 5:28 am

    An intriguing discussion is definitely worth comment. I do think that you need to publish more about this issue,
    it may not be a taboo subject but typically folks don’t talk
    about such subjects. To the next! All the best!!

  5. July 31, 2014 at 6:55 am

    I was wondering if you ever considered changing the layout of your
    site? Its very well written; I love what youve got to say.

    But maybe you could a little more in the way of content
    so people could connect with it better. Youve
    got an awful lot of text for only having one or two images.
    Maybe you could space it out better?

  6. July 31, 2014 at 8:01 am

    It’s actually a nice and helpful piece of information. I’m glad that you just
    shared this useful info with us. Please stay us
    up to date like this. Thanks for sharing.

  7. August 21, 2014 at 2:40 am

    Heya i’m for the first time here. I came across this board and I find It really useful & it helped me out a lot.

    I hope to give something back and aid others like you aided me.

  8. February 1, 2015 at 12:12 am

    Normally I do noot read article on blogs, bbut I wish to ssay that this write-upvery pressured me to try and do so!
    Your writing style has been amazed me. Thank you, quite
    nice article.

  1. October 2, 2014 at 6:42 pm

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

%d bloggers like this: