I used another approach recently that might be simpler than the semaphore approach, just define an interface in an assembly that both appdomains can reference. Then create a class that implements that interface and derivces from MarshalByRefObject
The interface would be whatever, note that any arguments to any methods in the interface will have to be serialized when the call goes over the appdomain boundary
/// <summary>
/// An interface that the RealtimeRunner can use to notify a hosting service that it has failed
/// </summary>
public interface IFailureNotifier
{
/// <summary>
/// Notify the owner of a failure
/// </summary>
void NotifyOfFailure();
}
Then in an assembly that the parent appdomain can use I define an implementation of that interface that derives from MarshalByRefObject:
/// <summary>
/// Proxy used to get a call from the child appdomain into this appdomain
/// </summary>
public sealed class FailureNotifier: MarshalByRefObject, IFailureNotifier
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
#region IFailureNotifier Members
public void NotifyOfFailure()
{
Log.Warn("Received NotifyOfFailure in RTPService");
// Must call from threadpool thread, because the PerformMessageAction unloads the appdomain that called us, the thread would get aborted at the unload call if we called it directly
Task.Factory.StartNew(() => {Processor.RtpProcessor.PerformMessageAction(ProcessorMessagingActions.Restart, null);});
}
#endregion
}
So when I create the child appdomain I simply pass it an instance of new FailureNotifier(). Since the MarshalByRefObject was created in the parent domain then any calls to its methods will automatically get marshalled over to the appdomain it was created in regardless of what appdomain it was called from. Since the call will be happening from another thread whatever the interface method does will need to be threadsafe
_runner = RealtimeRunner.CreateInNewThreadAndAppDomain(
operationalRange,
_rootElement.Identifier,
Settings.Environment,
new FailureNotifier());
...
/// <summary>
/// Create a new realtime processor, it loads in a background thread/appdomain
/// After calling this the RealtimeRunner will automatically do an initial run and then enter and event loop waiting for events
/// </summary>
/// <param name="flowdayRange"></param>
/// <param name="rootElement"></param>
/// <param name="environment"></param>
/// <returns></returns>
public static RealtimeRunner CreateInNewThreadAndAppDomain(
DateTimeRange flowdayRange,
byte rootElement,
ApplicationServerMode environment,
IFailureNotifier failureNotifier)
{
string runnerName = string.Format("RealtimeRunner_{0}_{1}_{2}", flowdayRange.StartDateTime.ToShortDateString(), rootElement, environment);
// Create the AppDomain and MarshalByRefObject
var appDomainSetup = new AppDomainSetup()
{
ApplicationName = runnerName,
ShadowCopyFiles = "false",
ApplicationBase = Environment.CurrentDirectory,
};
var calcAppDomain = AppDomain.CreateDomain(
runnerName,
null,
appDomainSetup,
new PermissionSet(PermissionState.Unrestricted));
var runnerProxy = (RealtimeRunner)calcAppDomain.CreateInstanceAndUnwrap(
typeof(RealtimeRunner).Assembly.FullName,
typeof(RealtimeRunner).FullName,
false,
BindingFlags.NonPublic | BindingFlags.Instance,
null,
new object[] { flowdayRange, rootElement, environment, failureNotifier },
null,
null);
Thread runnerThread = new Thread(runnerProxy.BootStrapLoader)
{
Name = runnerName,
IsBackground = false
};
runnerThread.Start();
return runnerProxy;
}