Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Improve cross-AppDomain communication using JointCode.Shuttle

0.00/5 (No votes)
27 Jul 2017 1  
JointCode.Shuttle is a fast, flexible and easy-to-use service-oriented framework for cross-AppDomain communication. It's a replacement for MarshalByrefObject which is provided by the runtime libraries.

Download JointCode.Shuttle and samples

Introduction

This article compares performance of two different technologies for making cross-AppDomain calls.

Background

Cross-AppDomain calls can be terrible. Tests show that cross-AppDomain method calls is hundreds to a thousand times slower than normal method calls within a same AppDomain.

Now we know that when an object is passed from an execution boundry (AppDomain) to another, it needs to be converted into a form that can be transferred; and then when it arrives, it’s rebuilt and converted back to the original form in memory. This process is called ‘marshaling’.

For example, in .NET Remoting, a MarshalByrefObject-derived object will be packed into a serializable ObjRef instance, this is what ‘ByRef’ in the type name means, it refers to the form of ObjRef.

By marshaling, it incurs too much overhead through the way, like reflections, security checks, serialization/deserialization, etc. With so many operations involved in, the performance punishment is inevitable, and unfortunately, we don’t have much change to optimize it.

As such, if you care about performance, it's recommended that you keep away from AppDomains, as we always do usually. But sometimes, you just needs to use another AppDomain, for example when you are implementing a plug-in architecture and needs to load and unload assemblies at runtime without stopping the application.

In these situations, a fast cross-AppDomain marshaler would be much helpful. This is where the JointCode.Shuttle comes in.

How to use it

In the JointCode.Shuttle distribution package, it contains two files: JointCode.Shuttle.dll and JointCode.Shuttle.Library.dll, where JointCode.Shuttle.dll is a library written in managed language, and JointCode.Shuttle.Library.dll is a component written in non-managed language, which is depended by the former.

Prepare

To use JointCode.Shuttle, we first need to refer to JointCode.Shuttle.dll in our project, and copy the JointCode.Shuttle.Library.dll to the folder where JointCode.Shuttle.dll is compiled (for example, after the project is compiled, if the JointCode.Shuttle.dll is copied to the c:/projects/sampleproject folder, you need to manually copy the JointCode.Shuttle.Library.dll to this folder).

Using the code

As the JointCode.Shuttle is interface-oriented, so we first need to create a service interface (also called service contract), and apply ServiceInterface attribute to it.

[ServiceInterface]
public interface IServiceFunctionTest
{
    // properties and methods ...
}

Then we create a service class that implements the contract and apply the ServiceClass attribute to it.

[ServiceClass(typeof(IServiceFunctionTest), Lifetime = LifetimeEnum.Transient)]
public class ServiceFunctionTest : IServiceFunctionTest
{
    // properties and methods
}

Because we want to make cross-AppDomain calls, we need to write a class for starting/stopping the remote services and let it inherit from MarshalByRefObject.

/// <summary>
/// A remote service end
/// </summary>
/// <seealso cref="System.MarshalByRefObject" />
public abstract class RemoteServiceEnd : MarshalByRefObject
{
    protected ShuttleDomain _shuttleDomain;

    /// <summary>
    /// Creates a ShuttleDomain instance in current AppDomain, so that we can communicate with other AppDomains.
    /// </summary>
    public void CreateShuttleDomain()
    {
        var key = this.GetType().Name;
        _shuttleDomain = ShuttleDomainHelper.Create(key, key);
    }

    /// <summary>
    /// Unregister all services registered to the global registry from the current AppDomain, and disposes the ShuttleDomain.
    /// </summary>
    public void DisposeShuttleDomain()
    {
        if (_shuttleDomain != null)
            _shuttleDomain.Dispose();
    }

    /// <summary>
    /// Registers services to a global registy from current AppDomain.
    /// </summary>
    public abstract void RegisterServices(); // provide services

    /// <summary>
    /// Consumes services registered by other providers in other AppDomains.
    /// </summary>
    public abstract void ConsumeServices(); // consume services
}

public class RemoteServiceEnd1 : RemoteServiceEnd
{
    public override void RegisterServices()
    {
        var guid = Guid.NewGuid();
        _shuttleDomain.RegisterServiceGroup(ref guid,
            new ServiceTypePair(typeof(ICommonService), typeof(CommonService)),
            new ServiceTypePair(typeof(IServiceFunctionTest), typeof(ServiceFunctionTest)));
    }

    public override void ConsumeServices()
    {
        IFakeService fakeService;
        if (_shuttleDomain.TryGetService(out fakeService))
        {
            Console.WriteLine("AppDomain [{0}], before calling the remote service: ", AppDomain.CurrentDomain.FriendlyName);
            var result = fakeService.PrintAndReturn("JointCode.Shuttle");
            Console.WriteLine("AppDomain [{0}], after calling the remote service with result [{1}] ", AppDomain.CurrentDomain.FriendlyName, result);
            Console.WriteLine();
        }
    }
}

Now, we are ready to use JointCode.Shuttle, like this:

// To be able to make inter-AppDomain communication with JointCode.Shuttle, firstly we must 
// initialize the ShuttleDomain.
// It doesn't matter whether the initialization operation is done in default AppDomain or 
// any other AppDomains, but it must be done before any ShuttleDomain instance is created.
ShuttleDomain.Initialize();

// Create a remote AppDomain
_remoteDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, null);

// Create a service end, which will be used as a proxy to operate the remote AppDomain
_serviceEnd1 = (RemoteServiceEnd)_remoteDomain.CreateInstanceAndUnwrap
    (_serviceEnd1AsmName, ServiceEnd1Type);

// Create a ShuttleDomain instance in remote AppDomain
_serviceEnd1.CreateShuttleDomain();

var key = Guid.NewGuid().ToString();
// Create a ShuttleDomain instance in current AppDomain
_shuttleDomain = ShuttleDomainHelper.Create(key, key);

// Register services through the above ShuttleDomain instance in remote AppDomain
_serviceEnd1.RegisterServices();

// Get a remote service instance in current AppDomain, which is created in remote AppDomain
_shuttleDomain.TryGetService(out _shuttleFunctionTest);

// Call the remote service method from current AppDomain
_shuttleFunctionTest.CallSimpleMethod();

Test

To compare the cross-AppDomain calling performance of JointCode.Shuttle and MarshalByrefObject and try to understand the various factors that might affect performance, I built a test. This test showed us two kind of benchmarks:

  1. Create remote service object and call object method repeatedly: This is to compare the performance of both calls as a whole, because the object creating and method invocation all costs.
  2. Create a remote service object and cache it into a local field, and then repeatedly call the object method: This is to measure the performance of method invocations with different kind and number of parameters, because the parameters needs to be marshaled across AppDomain, and that costs too. Please note that this is only for the convenience of testing, never cache remote service object to the field in practise. Because the lifetime of remote service object is controlled by the remote end, it is possible that the remote object has been expired and garbage collected at remote end without known by the local caller. Under this circumstance, if we continues to call its method, an exception will be thrown.

You can check out the full code for the benchmark application in the source code download. Here is the screenshot of part of the result :

JointCode.Shuttle vs MarshalByrefObject performance

Conclusion

The JointCode.Shuttle can significantly improve the performance of cross-AppDomain communication, and it brings many benefits that other technologies can not provide.

Future

JointCode.Shuttle is still a young project, and it lacks some features, such as service registering/unregistering notification, cross-AppDomain events, etc. The author will continue to improve existing functions, as well as introduce more new features in the future, but there are some limitations right now, including:

  1. Only supports 32-bit applications (x86 target platforms)
  2. Only supports Windows (only supports for .net framework, no mono support at this time)
  3. No events supported in the service interface
  4. No support for cross-AppDomain events
  5. Not thoroughly tested

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here