我有一个命令对象,根据请求队列的请求进行工作。此特定命令将在子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尝试访问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参数)。
答案 0 :(得分:22)
自从我查看任何跨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.
}
答案 1 :(得分:0)
假设您的代理类型是单一责任,实际上有一种更简单的方法来克服这一障碍。我当然假设你维护一个你创建的域的集合,并在你的应用程序关闭或你的包含对象被处置时卸载它们。我还假设你需要取消令牌的原因是在你的编组引用类型中取消一些异步操作。 您只需要执行以下操作:
创建tokenSource和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();
}