15

リクエスト キューからのリクエストに基づいて作業を行うコマンド オブジェクトがあります。この特定のコマンドは、子 appdomain でその作業を実行します。子 appdomain での作業の一部には、ConcurrentQueue 操作 (Add や Take など) のブロックが含まれます。リクエスト キューを介して子アプリケーション ドメインにアボート シグナルを伝達し、その中のワーカー スレッドをウェイクアップできる必要があります。

したがって、AppDomain の境界を越えて CancellationToken を渡す必要があると思います。

MarshalByRefObject から継承するクラスを作成してみました:

protected class InterAppDomainAbort : MarshalByRefObject, IAbortControl
    {
        public InterAppDomainAbort(CancellationToken t)
        {
            Token = t;
        }

        [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
        public override object InitializeLifetimeService()
        {
            return null;
        }

        public CancellationToken Token
        {
            get;
            private set;
        }

    };

これをワーカー関数の引数として渡します。

// cts is an instance variable which can be triggered by another thread in parent appdomain
cts = new CancellationTokenSource();
InterAppDomainAbort abortFlag = new InterAppDomainAbort(cts.Token);
objectInRemoteAppDomain = childDomain.CreateInstanceAndUnwrap(...);

// this call will block for a long while the work is being performed.
objectInRemoteAppDomain.DoWork(abortFlag);

しかし、objectInRemoteAppDomain が Token getter プロパティにアクセスしようとすると、例外が発生します。

System.Runtime.Serialization.SerializationException: Type 'System.Threading.CancellationToken' in Assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable.

私の質問は、.NET 同時実行データ構造 (CancellationToken 引数がサポートされている場所) でブロックされる可能性のあるアプリケーション ドメインとウェイクアップ スレッド間で中止/キャンセル シグナルを伝達するにはどうすればよいかということです。

4

2 に答える 2

23

クロス AppDomain のものを見てからしばらく経っているので、このコードには気付いていない問題があるかもしれませんが、うまく機能しているようです。基本的な問題は、ある AppDomain から別の AppDomain に CancellationToken[Source] を転送する方法がないように見えることです。そこで、必要に応じてセカンダリをキャンセルするようにプライマリを設定して、2 つのソースを作成します。

このシナリオに 2 つの別々のトークン ソースがあるという事実はもちろん問題になる可能性がありますが、シリアル化可能性の欠如が 2 つの別々の AppDomains で同じものを使用できないという事実を回避しているとは思いません。

Dispose最小限のエラー チェック、実装などに関する標準的な警告。

// I split this into a separate interface simply to make the boundary between
// canceller and cancellee explicit, similar to CancellationTokenSource itself.
public interface ITokenSource
{
    CancellationToken Token { get; }
}

public class InterAppDomainCancellable: MarshalByRefObject,
                                        ITokenSource,
                                        IDisposable
{
    public InterAppDomainCancellable()
    {
        cts = new CancellationTokenSource();
    }

    public void Cancel() { cts.Cancel(); }

    // Explicitly implemented to make it less tempting to call Token
    // from the wrong side of the boundary.
    CancellationToken ITokenSource.Token { get { return cts.Token; } }

    public void Dispose() { cts.Dispose(); }

    private readonly CancellationTokenSource cts;
}

// ...

// Crucial difference here is that the remotable cancellation source
// also lives in the other domain.
interAppDomainCancellable = childDomain.CreateInstanceAndUnwrap(...);

var primaryCts = new CancellationTokenSource();
// Cancel the secondary when the primary is cancelled.
// CancellationToken.Register returns a disposable object which unregisters when disposed.
using (primaryCts.Token.Register(() => interAppDomainCancellable.Cancel()))
{
    objectInRemoteAppDomain = childDomain.CreateInstanceAndUnwrap(...);
    // DoWork expects an instance of ITokenSource.
    // It can access Token because they're all in the same domain together.
    objectInRemoteAppDomain.DoWork(interAppDomainCancellable);
    // ... some other work which might cancel the primary token.
}
于 2013-03-02T21:38:16.840 に答える
1

実際には、プロキシ タイプが単一の責任であると仮定すると、この障害を克服するためのはるかに簡単な方法があります。もちろん、作成したドメインのコレクションを維持し、アプリケーションを閉じたり、含まれているオブジェクトを破棄したりした場合にそれらをアンロードすると想定しています。また、キャンセル トークンが必要な理由は、マーシャリングされた参照型の非同期操作をキャンセルするためだと思います。次の手順を実行するだけです。

tokenSource フィールドと token フィールドを作成し、コンストラクターで初期化します。

_cancellationTokenSource = new CancellationTokenSource();
_token = _cancellationTokenSource.Token;

以下のイベントにサブスクライブします。UnhandledException は、ドメインを途中で閉じる原因となる障害のある例外をキャッチする目的に役立ちます。これはベスト プラクティスです。

var currDomain = AppDomain.CurrentDomain;
            currDomain.DomainUnload += currDomain_DomainUnload;
            currDomain.UnhandledException += currDomain_UnhandledException;

ドメインのアンロード イベントが呼び出されたときに、トークン ソースでキャンセルを呼び出します。さらに、いずれかから呼び出されるドメイン イベントのサブスクライブを解除する dispose メソッドが必要な場合や、ドメインのクリーンアップにガベージ コレクションを処理させる場合があります。

void currDomain_DomainUnload(object sender, EventArgs e)
    {
        _log.Debug(FormatLogMessage(_identity, "Domain unloading Event!"));
        _cancellationTokenSource.Cancel();
        _logPlayer.Dispose();
    }

 void currDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        _log.Error(string.Format("***APP Domain UHE*** Error:{0}", e.ExceptionObject);
        _cancellationTokenSource.Cancel();
        _logPlayer.Dispose();
    }
于 2015-10-21T15:18:00.853 に答える