2013-03-01 37 views
12

我有一个命令对象,根据来自请求队列的请求进行工作。这个特定的命令将在一个子appdomain中执行它的工作。在儿童appdomain中完成其工作的一部分涉及对ConcurrentQueue操作(例如,添加或取出)的阻塞。我需要能够通过请求队列传播中止信号,传递给子appdomain,并唤醒其中的工作线程。如何通过AppDomain边界传递CancellationToken?

因此,我认为我需要跨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尝试访问令牌获取器属性:

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参数)中被阻塞的线程。

回答

19

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

在这种情况下两个独立的令牌来源可能当然是一个问题,但我不认为你身边缺乏serialisability的阻止您使用在同一个事实越来越事实反正两个独立的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. 
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); 
+0

就我所知,这是完美的解决方案。 – usr 2013-03-02 22:38:53

0

标准告诫实际上有一个更容易克服这一障碍假设你的代理类型的方式是单一的责任。当然,我假设你维护一个你创建的域的集合,并在你的应用程序被关闭或者你的包含对象被丢弃时卸载它们。我还假设您需要取消标记的原因是取消您编组引用类型中的某些异步操作。 您只需执行以下操作:

创建您的tokenSource和标记字段并在构造函数中初始化它们。

_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(); 
    } 
+0

聪明。非常好。 – gap 2016-06-14 11:40:05

相关问题