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()はスターターにのみ認識されているタイプのメソッドであり、appdomainには存在しないため、これによりアセンブリが見つからないという例外が発生します。

OnClosedItselfをデリゲートとしてシリアル化可能なクラスにラップしても、同じです。

助言がありますか?

編集:私がやろうとしているのは、自己更新タスクを構築することです。そのため、新しいバージョンが利用可能になった場合にタスクを停止して再作成できるスターターを作成しました。ただし、タスクが他の場所から停止された場合は、スターターに終了するように通知する必要もあります。

//質問から多くの一時的なコードを削除しました

編集2:ハプロは私を正しい方向に向けました。セマフォを使用してコールバックを実装できました。

4

3 に答える 3

3

この状況は、共有タイプ(この場合はIBoostrapperの実装)を持つ3番目のアセンブリを使用して解決しました。私の場合、より多くのタイプとロジックがありましたが、1つのタイプだけのアセンブリを用意するのは少しやり過ぎかもしれません...

たぶんあなたはMutexという名前の共有を使用したいですか?次に、2つのAppDomainsタスクを同期できます...

編集:

メインのAppdomainで、また最初に所有されたミューテックスを作成しているため、すでに所有しているため、WaitOne()でミューテックスが停止することはありません。

たとえば、最初に所有されていたように、IBootstrapper実装クラス内の生成されたAppdomainにMutexを作成できます。CreateInstanceAndUnwrap呼び出しが戻った後、ミューテックスが存在し、Bootstrapperによって所有されている必要があります。これで、ミューテックスを開くことができ(OpenExistingを呼び出して、共有していることを確認できます)、WaitOneを使用できます。生成されたAppDomainブートストラッパーが完了すると、ミューテックスを解放でき、メインのAppdomainが作業を完了します。

ミューテックスはシステム全体であるため、プロセスやAppDomain間で使用できます。MSDNMutex備考セクションをご覧ください

編集:ミューテックスで機能させることができない場合は、セマフォを使用した次の短い例を参照してください。これは概念を説明するためのものであり、追加のアセンブリなどはロードしていません。デフォルトの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

最近、セマフォアプローチよりも単純な別のアプローチを使用しました。両方のアプリドメインが参照できるアセンブリ内のインターフェイスを定義するだけです。次に、そのインターフェイスを実装し、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;
}
于 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 に答える