在.net中实现ApplicationDomain-boundary的回调

时间:2011-04-14 16:49:16

标签: c# .net applicationdomain

我使用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: 哈普洛向我指出了正确的方向。我能够用信号量实现回调。

3 个答案:

答案 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();
}