15

我有一个命令对象,根据来自请求队列的请求进行工作。此特定命令将在子 appdomain 中执行其工作。在子应用程序域中完成其工作的一部分涉及阻塞 ConcurrentQueue 操作(例如,添加或获取)。我需要能够通过请求队列将中止信号传播到子应用程序域,并唤醒其中的工作线程。

因此,我认为我需要跨 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.

我的问题是:如何在 appdomains 中传播中止/取消信号并唤醒可能在 .NET 并发数据结构中被阻塞的线程(支持 CancellationToken 参数)。

4

2 回答 2

23

自从我查看任何跨 AppDomain 的东西以来已经有一段时间了,所以这段代码可能存在我没有意识到的问题,但它似乎可以完成这项工作。根本问题是似乎没有办法将 CancellationToken[Source] 从一个 AppDomain 转移到另一个 AppDomain。所以我创建了两个来源,主要设置为在适当时取消次要来源。

在这种情况下有两个单独的令牌源这一事实当然可能是一个问题,但我认为您并没有解决这样一个事实,即缺乏可序列化性会阻止您在两个单独的 AppDomain 中使用相同的一个。

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 回答