Search code examples
.neteventsappdomain.net-remoting

How do I communicate an event between App Domains?


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?


Solution

  • 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");
        }
    }