我使用Applicationdomain动态加载一个dll,以便在nessesary时卸载。如果加载的dll中的任务自行终止,我无法工作的是来自创建的Appdomain的回调方法。
到目前为止我有什么
public interface IBootStrapper
{
void AsyncStart();
void StopAndWaitForCompletion();
event EventHandler TerminatedItself;
}
和“初学者”一侧
private static Procedure CreateDomainAndStartExecuting()
{
AppDomain domain = AppDomain.CreateDomain("foo", null, CODEPATH, string.Empty, true);
IBootStrapper strapper = (IBootStrapper)domain.CreateInstanceAndUnwrap(DYNAMIC_ASSEMBLY_NAME, CLASSNAME);
strapper.ClosedItself += OnClosedItself;
strapper.AsyncStart();
return delegate
{
strapper.StopAndWaitForCompletion();
AppDomain.Unload(domain);
};
}
导致程序集未找到异常,因为OnClosedItself()是一个仅为Starter所知的类型的方法,该方法在appdomain中不存在。
如果我将OnClosedItself作为委托包装在一个可序列化的类中,它就是一样的。
有什么建议吗?
编辑: 我正在尝试做的是建立一个自我更新的任务。因此我创建了一个启动器,如果有新版本可以停止并重新创建任务。但是如果任务从其他地方停止,它也应该通知启动器终止。
//从问题中剥离了大量临时代码
编辑2: 哈普洛向我指出了正确的方向。我能够用信号量实现回调。
答案 0 :(得分:3)
我通过使用具有共享类型的第三个程序集解决了这种情况(在您的情况下是IBoostrapper的实现)。在我的情况下,我有更多的类型和逻辑,但对于你来说,只有一种类型的程序集可能有点过分......
也许您更愿意使用共享名为Mutex ?然后,您可以同步2个AppDomains任务...
编辑:
您正在主Appdomain上创建互斥锁,也是最初拥有,因此它永远不会停留在WaitOne()上,因为您已经拥有它。
例如,您可以在IBootstrapper实现类中生成的Appdomain上创建Mutex,如最初拥有的那样。在 CreateInstanceAndUnwrap 调用返回后,互斥锁应该存在,并且它由Bootstrapper拥有。因此,您现在可以打开互斥锁(调用 OpenExisting ,这样您就可以确保共享它了),然后就可以使用WaitOne了。生成的AppDomain引导程序完成后,您可以释放互斥锁,主Appdomain将完成工作。
互斥锁是系统范围的,因此可以跨进程和AppDomain使用它们。请查看MSDN Mutex
的备注部分编辑:如果你无法使用互斥锁,请参阅下一个使用信号量的简短示例。这只是为了说明这个概念,我没有加载任何额外的程序集等等。默认AppDomain中的主线程将等待从生成的域释放信号量。当然,如果您不希望主AppDomain终止,则不应允许主线程退出。
class Program
{
static void Main(string[] args)
{
Semaphore semaphore = new Semaphore(0, 1, "SharedSemaphore");
var domain = AppDomain.CreateDomain("Test");
Action callOtherDomain = () =>
{
domain.DoCallBack(Callback);
};
callOtherDomain.BeginInvoke(null, null);
semaphore.WaitOne();
// Once here, you should evaluate whether to exit the application,
// or perform the task again (create new domain again?....)
}
static void Callback()
{
var sem = Semaphore.OpenExisting("SharedSemaphore");
Thread.Sleep(10000);
sem.Release();
}
}
答案 1 :(得分:3)
我最近使用了另一种方法,它可能比信号量方法更简单,只需在程序集中定义一个appdomains都可以引用的接口。然后创建一个实现该接口的类,并从MarshalByRefObject派生
接口可以是任何东西,注意当调用越过appdomain边界时,接口中任何方法的任何参数都必须被序列化
/// <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();
}
然后在父appdomain可以使用的程序集中,我定义了一个派生自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
}
因此,当我创建子appdomain时,我只是传递一个新的FailureNotifier()实例。由于MarshalByRefObject是在父域中创建的,因此对其方法的任何调用都将自动编组到创建它的appdomain,而不管它是从哪个appdomain调用。由于调用将从另一个线程发生,无论接口方法需要什么线程安全
_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;
}
答案 2 :(得分:1)
感谢Haplo我能够实现如下的同步
// In DYNAMIC_ASSEMBLY_NAME
class Bootstrapper : IBootStrapper
{
public void AsyncStart()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
m_task = new MyTask();
m_thread = new Thread(delegate()
{
m_task.Run();
if (m_task.Completed)
Semaphore.OpenExisting(KeepAliveStarter.SEMAPHORE_NAME).Release();
});
thread.Start();
}
public void StopAndWaitForCompletion()
{
m_task.Shutdown();
m_thread.Join();
}
}
// in starter
private static Procedure CreateDomainAndStartExecuting()
{
AppDomain domain = AppDomain.CreateDomain("foo", null, CODEPATH, string.Empty, true);
IBootStrapper strapper = (IBootStrapper)domain.CreateInstanceAndUnwrap(DYNAMIC_ASSEMBLY_NAME, CLASSNAME);
strapper.AsyncStart();
return delegate
{
strapper.StopAndWaitForCompletion();
AppDomain.Unload(domain);
};
}
static void Main(string[] args)
{
var semaphore = new Semaphore(0, 1, KeepAliveStarter.SEMAPHORE_NAME);
DateTime lastChanged = DateTime.MinValue;
FileSystemEventHandler codeChanged = delegate
{
if ((DateTime.Now - lastChanged).TotalSeconds < 2)
return;
lastChanged = DateTime.Now;
Action copyToStopCurrentProcess = onStop;
onStop = CreateDomainAndStartExecuting();
ThreadPool.QueueUserWorkItem(delegate
{
copyToStopCurrentProcess();
});
};
FileSystemWatcher watcher = new FileSystemWatcher(CODEPATH, ASSEMBLY_NAME + ".dll");
watcher.Changed += codeChanged;
watcher.Created += codeChanged;
onStop = CreateDomainAndStartExecuting();
watcher.EnableRaisingEvents = true;
semaphore.WaitOne();
onStop();
}