Run Anonymous Methods in Another AppDomain

I’ve been working on a small project where I was creating a plugin-based system and needed to execute a small snippet of code in another AppDomain.  Normally there is a lot of plumbing to get this to work, so I figured it might be useful to abstract out some of the implementation into an easy-to-use generic method.  Here’s what I came up with. :)

The standard way to execute methods in another AppDomain is to create a class that inherits from MarshalByRefObject and put those methods in that class.  Typically this class will also override InitializeLifetimeService and return null, implying an infinite timeout.  The reasoning is that .NET is using Remoting under the covers and creates a proxy object that knows how to talk to the remote object.  It doesn’t matter if the communication needs to happen between two AppDomains across machine/network boundaries or between two AppDomains on the same machine — the underlying pattern is the same.  If you don’t return null here then the .NET Runtime may magically decide to tear down your proxy class and its connection to the remote object after a certain amount of time (I think the default is 5 minutes).  It makes sense to think of this remote class as a "sandbox" that can execute methods.

So, we define a RemoteSandbox class that will live in the new AppDomain that inherits from MarshalByRefObject, implements InitializeLifetimeService, and has the ability to execute a Func<Tin,Tout> delegate that we specify.

    public class RemoteSandbox<Tin, Tout> : MarshalByRefObject

    {

        public RemoteSandbox()

        {

        }

 

        public Tout Execute(Tin input, Func<Tin, Tout> method)

        {

            return method(input);

        }

 

        public override object InitializeLifetimeService()

        {

            return null;

        }

    }

Now, on the other end of the wire (the local end), we need to go through the motions of creating a new AppDomain, creating a new RemoteSandbox object in the new AppDomain, and specifying an anonymous method that gets passed to the Execute method.  Here is that helper method in all of its tediousness.  You can see why it was a good candidate for refactoring. :)

    public static class AppDomainHelper

    {

        public static Tout ExecuteInNewAppDomain<Tin, Tout>(

            Tin input, Func<Tin, Tout> method)

        {

            // Set up the new AppDomain’s ApplicationBase

            // to be the assembly’s current directory

            AppDomainSetup setup = new AppDomainSetup();

            setup.ApplicationBase =

                Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

 

            // TODO: Allow more restrictive permission sets to be used

            PermissionSet grantSet =

                PolicyLevel.CreateAppDomainLevel().GetNamedPermissionSet("FullTrust");

            AppDomain appDomain = AppDomain.CreateDomain(

                "AppDomainHelper.ExecuteInNewAppDomain", // FriendlyName

                null, // Evidence

                setup,

                grantSet,

                null); // FullTrust assemblies

 

            Type sandboxType = typeof(RemoteSandbox<Tin, Tout>);

            // In the format "Name.Space" (no file extension!)

            string sandboxAssemblyName = sandboxType.Assembly.GetName().Name;

            // In the format "Name.Space.ClassName"

            string sandboxTypeName = sandboxType.FullName;

 

            // Create a new sandbox

            RemoteSandbox<Tin, Tout> sandbox =

                (RemoteSandbox<Tin, Tout>)appDomain.CreateInstanceAndUnwrap(

                sandboxAssemblyName,

                sandboxTypeName);

 

            // Serialize the method delegate to the new sandbox,

            // execute it, and serialize the result back here

            Tout output = sandbox.Execute(input, method);

 

            // Unload the AppDomain since we’re done using it

            AppDomain.Unload(appDomain);

 

            return output;

        }

    }

And there you have it.  Using it is extremely simple:

        static void Test1()

        {

            Console.WriteLine("Main executing with Id={0}",

                AppDomain.CurrentDomain.Id);

            int length = AppDomainHelper.ExecuteInNewAppDomain(

                "This is a test.",

                input =>

                {

                    Console.WriteLine("Now executing with Id={0}",

                        AppDomain.CurrentDomain.Id);

                    return input.Length;

                });

            Console.WriteLine("The string has Length={0}", length);

            Console.ReadLine();

        }

This produces the output:

AppDomainHelper_output

Cool!  I think that really cuts down on the amount of plumbing classes/code that I need to write.

Note of caution: Only types that are marked as Serializable can pass back and forth with the new AppDomain. If you try something funny, you’ll get a SerializationException that says "Type ‘___’ in assembly ‘___’ is not marked as serializable."

It is for that exact reason that this approach doesn’t work with anonymous types, unfortunately.  Let’s say that I wanted to do something similar to the ThreadPool.QueueUserWorkItem approach:

        static void Test2()

        {

            Console.WriteLine("Main executing with Id={0}", AppDomain.CurrentDomain.Id);

 

            string message = AppDomainHelper.ExecuteInNewAppDomain(

                new {Name="Matt", Age=26},

                person =>

                {

                    Console.WriteLine("Now executing with Id={0}", AppDomain.CurrentDomain.Id);

                    return String.Format("Welcome to the new AppDomain, {0}", person.Name);

                });

            Console.WriteLine("The message is: {0}", message);

            Console.ReadLine();

        }

Notice that instead of passing a string (or any other Serializable type), I’m passing an anonymous type with two properties, Name and Age.  The code compiles just fine, but throws a runtime SerializationException that says "Type ‘<>f__AnonymousType0`2′ in assembly ‘AppDomainHelper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ is not marked as serializable."  Sure enough, opening up the assembly in .NET Reflector shows that the compiler-generated class isn’t marked as serializable:

        [CompilerGenerated, DebuggerDisplay(@"\{ Name = {Name}, Age = {Age} }", Type="<Anonymous Type>")]

        internal sealed class <>f__AnonymousType0<<Name>j__TPar, <Age>j__TPar>

        {

            // Fields

            [DebuggerBrowsable(DebuggerBrowsableState.Never)]

            private readonly <Age>j__TPar <Age>i__Field;

            [DebuggerBrowsable(DebuggerBrowsableState.Never)]

            private readonly <Name>j__TPar <Name>i__Field;

 

            // Methods

            [DebuggerHidden]

            public <>f__AnonymousType0(<Name>j__TPar Name, <Age>j__TPar Age);

            [DebuggerHidden]

            public override bool Equals(object value);

            [DebuggerHidden]

            public override int GetHashCode();

            [DebuggerHidden]

            public override string ToString();

 

            // Properties

            public <Age>j__TPar Age { get; }

            public <Name>j__TPar Name { get; }

        }

Bummer!  There’s really no way to change that without convincing the C# compiler authors to change how the magic of anonymous types is actually implemented. :)

If anyone from the C# Compiler Team out there is reading this, consider this a feature request. :)   That is, if any anonymous type is created where all of the member properties are marked as Serializable, also mark the anonymous type’s class as Serializable.  This would allow anonymous types to be serialized across AppDomains.

Look for a future post about how the RemoteSandbox can call methods that get remoted back to the originating AppDomain to support things like logging or progress updates. :)

Hope that helps someone!


You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

AddThis Social Bookmark Button

3 Responses to “Run Anonymous Methods in Another AppDomain”

  1. […] Run Anonymous Methods in Another AppDomain […]

  2. Hey matt,
    I’ve been trying something very similar.. and hit a problem when hitting the createInstanceUnwrap() method. It creates
    a transparentproxy without any problem, but it can’t be
    casted to the type.
    I tried your code to see what the difference would be..
    turns out.. I get the same error?!
    Did you come across this situation too?

    regards,
    Merijn

  3. Just in case someelse comes across the problem I encountered; figured it out…
    Despite that the type to which we would like to unwrap is known in the default appdomain.. it can have trouble figuring this out at runtime apparently. You can resolve this by binding the AppDomain.CurrentDomain.AssemblyResolve event (ie. AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(SomeAssemblyResolve) obviously…)
    the SomeAssemblyResolve will have an eventarg.Name.. this is the name of the assembly in question and can be loaded through System.Reflection.Assembly.Load(..) or Assemly.LoadFromPartialName(…) (although deprecated)..
    When this event is handled… your proxy will be correctly unwrap towards the expected class.

    Hope it helps someone :)

Leave a Reply