【问题标题】:implement callback over ApplicationDomain-boundary in .net在 .net 中实现对 ApplicationDomain-boundary 的回调
【发布时间】:2011-08-05 17:14:21
【问题描述】:

我使用应用程序域动态加载 dll 以在必要时卸载。如果加载的 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: Haplo 为我指明了正确的方向。我能够用信号量实现回调。

【问题讨论】:

  • 我对 appdomains 没有太多经验,但你不能告诉新的 appdomain 加载包含你的类型的程序集吗?还是包含两个应用程序域共享的类型的程序集?
  • @DarkSquirrel42 starter 和 dll 位于不同的位置和 domain.Load(Assembly.GetExecutingAssembly().GetName());仍然会出现丢失的 dll 异常

标签: c# .net applicationdomain


【解决方案1】:

我通过使用具有共享类型的 第三个程序集 解决了这种情况(在您的情况下是 IBoostrapper 的实现)。就我而言,我有更多的类型和逻辑,但对你来说,只为一种类型创建一个程序集可能有点矫枉过正......

也许您更愿意使用名为 Mutex 的共享?然后就可以同步2个AppDomains任务了……

编辑:

您正在主 Appdomain 上创建互斥锁,并且也是 最初拥有,因此它永远不会在 WaitOne() 上停止,因为您已经拥有它。

例如,您可以在 IBootstrapper 实现类中生成的 Appdomain 上创建 Mutex,就像最初拥有的一样。在 CreateInstanceAndUnwrap 调用返回后,互斥锁应该存在并且归引导程序所有。因此,您现在可以打开互斥体(调用 OpenExisting 以确保您正在共享它),然后您可以在其上使用 WaitOne。生成的 AppDomain 引导程序完成后,您可以释放互斥锁,主 Appdomain 将完成工作。

互斥锁是系统范围的,因此可以跨进程和 AppDomain 使用。看看MSDN Mutexremarks部分

编辑:如果您无法使其与互斥锁一起使用,请参阅下一个使用信号量的简短示例。这只是为了说明这个概念,我没有加载任何额外的程序集,等等。默认 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();
    }
}

【讨论】:

  • @Haplo 命名的 mutex 似乎很有希望,但我无法阻止它,WaitOne() 总是立即返回: static void Main(string[] args) { bool createdNew; var mutex = new Mutex(/*initialallyOwned*/ true, "SomeName", out createdNew); Console.WriteLine(createdNew); Console.WriteLine(mutex.WaitOne()); Console.WriteLine(mutex.WaitOne()); Console.Read();返回; }
  • 看起来同名的互斥锁(使用共享的 const 字符串)是不同的,因为它们不会在 waitone() 上阻塞,无论我从哪一边先调用 waitone()
  • @Firo - 我完成了我的回答
  • @Firo - 看一个例子stackoverflow.com/questions/229565/… 。另外,另一种方法是完全相同的,但不是使用互斥锁,而是使用最初设置为零的信号量。
  • @Haplo 我无法让互斥锁工作,它不会阻塞。还有另一个问题,当我卸载域时,互斥锁总是被释放,这会在加载程序集的每次更改时终止启动器
【解决方案2】:

我最近使用了另一种可能比信号量方法更简单的方法,只需在程序集中定义一个两个 appdomain 都可以引用的接口。然后创建一个实现该接口并从 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 时,我只需将 new 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;
}

【讨论】:

  • 再看一遍,也许接口本身并不是必须的,你可以在两个都可以引用的地方定义 MarshalByRefObject 类,使用接口可能会更好一些设计。子 appdomain 仍然需要加载该类型才能使用它。在我的场景中,所有 appdomains 都具有相同的当前目录并加载大部分相同的 dll,所以这不是问题。
  • 好主意,而且很有效。不过,我需要信号量来阻止退出启动器的主循环,所以我接受了它。否则我会走你的路。不幸的是我不能同时接受这两个答案
【解决方案3】:

感谢 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();
}

【讨论】:

    猜你喜欢
    • 2011-01-12
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多