5

I have two app domains, one parent creating the child domain. In the child domain, there is a MarshalByRef object, being communicated with using .NET Remoting. An object running in the parent domain invokes the wrapper for the remote object as part of the application's function:

public class ScanningTask : Task
{
    private class Loader : MarshalByRef
    {
        public void Load(IEnumerable<string> paths)
        {
            ...
        }

        public event EventHandler<LoadEventArgs> OnLoad;
    }

    public void RunTask()
    {
        var domain = AppDomain.CreateDomain("LoadDomain");

        var loader = (Loader)domain.CreateInstanceFromAndUnwrap(
            typeof(Loader).Assembly.Location,
            typeof(Loader).FullName);

        loader.Load(...);

        AppDomain.Unload(domain);
    }
}

Most code removed for brevity.

This Loader object exposes an OnLoad event I'd like to capture in the parent domain. If I just add an event handler delegate, it tries to serialize the ScanningTask into the child domain and throws an exception about it not being serializable.

What I really want is for the event to be communicated across the domains. Any clever suggestions as to how?

Paul Turner
  • 38,949
  • 15
  • 102
  • 166

1 Answers1

6

Based on this solution, you could make your Task class task inherit from MarshalByRefObject as well. This would solve the serialization issue as it would pass a cross-AppDomain serialized reference which would be used to attach to the event.

public class ScanningTask : MarshalByRefObject
{
    private class Loader : MarshalByRefObject
    {
        public void Load()
        {
            if (OnLoad != null)
                OnLoad(this, EventArgs.Empty);
        }

        public event EventHandler OnLoad;
    }

    public void RunTask()
    {
        var domain = AppDomain.CreateDomain("LoadDomain");

        var loader = (Loader)domain.CreateInstanceFromAndUnwrap(
            typeof(Loader).Assembly.Location,
            typeof(Loader).FullName);

        loader.OnLoad += new EventHandler(loader_OnLoad);
        loader.Load();

        AppDomain.Unload(domain);
    }

    void loader_OnLoad(object sender, EventArgs e)
    {
        Console.Write("load event called");
    }
}

If for existing codebase reasons the base class Task cannot be made to inherit from MarshalByRefObject, your solution could be a proxy class that inherits from Loader (therefore being a MarshalByRefObject itself) and forwards calls to an actual unwrapped instance.

public class ScanningTask
{
    private class Loader : MarshalByRefObject
    {
        public virtual void Load()
        {
            RaiseOnLoad(this);
        }

        protected void RaiseOnLoad(Loader loader)
        {
            if (OnLoad != null)
                OnLoad(loader, EventArgs.Empty);
        }

        public event EventHandler OnLoad;
    }

    private class LoaderProxy : Loader
    {
        public readonly Loader Instance;

        public LoaderProxy(Loader loaderInstance)
        {
            this.Instance = loaderInstance;
            this.Instance.OnLoad += new EventHandler((sender, e) => RaiseOnLoad(this.Instance));
        }

        public override void Load()
        {
            this.Instance.Load();
        }
    }

    public void RunTask()
    {
        var domain = AppDomain.CreateDomain("LoadDomain");

        var loader = (Loader)domain.CreateInstanceFromAndUnwrap(
            typeof(Loader).Assembly.Location,
            typeof(Loader).FullName);

        var proxy = new LoaderProxy(loader);
        proxy.OnLoad += new EventHandler(loader_OnLoad);
        loader.Load(); // same as proxy.Load()

        AppDomain.Unload(domain);
    }

    void loader_OnLoad(object sender, EventArgs e)
    {
        Console.Write("load event called");
    }
}
Community
  • 1
  • 1
D. A. Terre
  • 6,092
  • 1
  • 18
  • 18
  • Wrapping the task worked as a solution and it worked pretty well. I did encounter a problem resolving dependencies though; check the `AppDomain.AssemblyResolve` event if you get weird, inexplicable bugs! – Paul Turner May 04 '11 at 10:28
  • When creating your AppDomain, pass in an instance of AppDomainSetup to configure any assembly resolution customizations that you need. I believe that in the above scenario, your new AppDomain will be based in the directory within which the current executable resides. As such, if your dynamically loaded DLL is not in the same directory, you will get loader errors. So, remember to set the ApplicationBase path of your AppDomainSetup object to the location that contains all the necessary dependencies. – Adam May 09 '11 at 04:43
  • - remember, that registering an event will cause the 'dynamically loaded dll' to try to load the 'subscriber dll', so both DLLs must be resolveable in the child AppDomain. – Adam May 09 '11 at 04:48