C# 如何跨AppDomain边界传递CancellationToken?

C# 如何跨AppDomain边界传递CancellationToken?,c#,.net,concurrency,appdomain,abort,C#,.net,Concurrency,Appdomain,Abort,我有一个命令对象,根据请求队列中的请求执行工作。此特定命令将在子appdomain中执行其工作。在子appdomain中执行其工作的一部分涉及对ConcurrentQueue操作(例如,Add或Take)的阻塞。我需要能够通过请求队列将中止信号传播到子appdomain,并唤醒其中的工作线程 因此,我认为我需要跨AppDomain边界传递CancellationToken 我尝试创建一个继承自MarshalByRefObject的类: protected class InterAppDomain

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

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

    };
并将其作为参数传递给worker函数:

// 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尝试访问令牌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参数)中被阻止。

我已经有一段时间没有看过任何跨AppDomain的内容了,因此,这段代码可能存在一些我没有意识到的问题,但它似乎可以完成这项工作。根本问题是似乎没有办法将CancellationToken[源]从一个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.
}

假设代理类型是一个单独的职责,实际上有一种更容易克服此障碍的方法。当然,我假设您维护一个已创建域的集合,并在应用程序关闭或包含对象被释放时卸载它们。我还假设您需要取消令牌的原因是取消封送引用类型中的某些异步操作。 您只需执行以下操作:

创建令牌源和令牌字段,并在构造函数中初始化它们

_cancellationTokenSource = new CancellationTokenSource();
_token = _cancellationTokenSource.Token;
订阅以下活动。UnhandledException将用于捕获导致域过早关闭的任何错误异常。这应该是一种最佳做法

var currDomain = AppDomain.CurrentDomain;
            currDomain.DomainUnload += currDomain_DomainUnload;
            currDomain.UnhandledException += currDomain_UnhandledException;
调用域卸载事件时,对令牌源调用cancel。此外,您可能希望有一个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();
    }

就我所知,这是一个完美的解决方案。谢谢。
Token.Register
通过在worker对象上实现cancel方法帮助我解决了这个问题,这样我就不需要
interappdomaincancelable
类。小提示:可能要处理Register函数返回的CancellationTokenRegistration请参见:不知道如果不处理它是否会给您带来麻烦,但我猜它没有无缘无故地实现IDispose;-)聪明。很不错的。