5

我使用 Applicationdomain 动态加载 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 为我指出了正确的方向。我能够用信号量实现回调。

4

3 回答 3

3

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

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

编辑:

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

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

互斥锁是系统范围的,因此它们可以跨进程和 AppDomains 使用。看看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();
    }
}
于 2011-04-14T17:20:30.663 回答
3

我最近使用了另一种可能比信号量方法更简单的方法,只需在程序集中定义一个两个 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;
}
于 2011-04-26T15:28:10.593 回答
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();
}
于 2011-04-26T14:42:25.190 回答