【问题标题】:How do I pass CancellationToken across AppDomain boundary?如何跨 AppDomain 边界传递 CancellationToken?
【发布时间】:2013-02-15 10:49:44
【问题描述】:

我有一个命令对象,根据来自请求队列的请求进行工作。此特定命令将在子 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;
        }

    };

并将 this 作为参数传递给工作函数:

// 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 参数)。

【问题讨论】:

    标签: c# .net concurrency appdomain abort


    【解决方案1】:

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

    【讨论】:

    • 据我所知,这是完美的解决方案。
    • 谢谢。 Token.Register 通过在工作对象上实现取消方法帮助我解决了这个问题,这样我就不需要 InterAppDomainCancellable 类。小注意:也许处置 Register 函数返回的 CancellationTokenRegistration 参见:docs.microsoft.com/en-us/dotnet/api/… 不知道如果不这样做会不会给您带来麻烦,但我想它没有实现 IDispose 无缘无故 ;-)
    【解决方案2】:

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

    创建您的 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();
        }
    

    【讨论】:

    • 聪明。非常好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-04
    • 2011-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多