Недавно я использовал другой подход, который может быть проще, чем семафорный подход, просто определите интерфейс в сборке, на которую могут ссылаться оба домена приложений. Затем создайте класс, который реализует этот интерфейс и наследуется от MarshalByRefObject
Интерфейс будет любым, обратите внимание, что любые аргументы любых методов в интерфейсе должны быть сериализованы, когда вызов проходит через границу домена приложения
/// <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();
}
Затем в сборке, которую может использовать родительский домен приложения, я определяю реализацию этого интерфейса, производную от 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
}
Поэтому, когда я создаю дочерний домен приложения, я просто передаю ему экземпляр нового FailureNotifier (). Поскольку MarshalByRefObject был создан в родительском домене, любые вызовы его методов будут автоматически перенаправляться в домен приложения, в котором он был создан, независимо от того, из какого домена приложения он был вызван. Поскольку вызов будет происходить из другого потока независимо от метода интерфейса, он должен быть потокобезопасным
_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;
}