I have a command object, doing work based on a request from a request queue. This particular command will execute its work in a child appdomain. Part of doing its work in the child appdomain involves blocking on a ConcurrentQueue operation (eg, Add or Take). I need to be able to propagate an abort signal through the request queue, across to the child appdomain, and to wake up the worker threads therein.
Therefore, I think I need to pass a CancellationToken across the AppDomain boundary.
I tried creating a class which inherits from 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;
}
};
and passing this as an argument on the worker function:
// 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);
But I still get an exception when the objectInRemoteAppDomain tries to access the Token getter property:
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.
My question is: How can I propagate the abort/cancellation signal across the appdomains and wake up threads that may be blocked in .NET concurrency data structures (where CancellationToken arguments are supported).
There is actually a much easier way to overcome this obstacle assuming your proxy type is a single responsibility. I am assuming of course that you maintain a collection of your created domains and unload them should your application be closed or your containing object be disposed. I also assume the reason you need the cancellation token is to cancel some async operation in your marshaled reference type. You simply need to do the following:
Create your tokenSource and token fields and initialize them in your constructor.
Subscribe to the following events. The UnhandledException will serve the purpose of catching any faulting exception which causes your domain to close prematurely. This should be a best practice.
Call cancel on your token source when the domain unload event is called. Additionally you may want to have a dispose method that unsubscribes to the domain events that gets called from either or just let the domain cleanup process garbage collection.
It's been a while since I looked at any cross-AppDomain stuff, so there might be problems with this code that I haven't realised, but it seems to do the job. The fundamental problem is that there seems no way to transfer a CancellationToken[Source] from one AppDomain to another. So I create two sources, with the primary set up to cancel the secondary when appropriate.
The fact that there are two separate token sources in this scenario could of course be a problem, but I don't think you're getting around the fact that lack of serialisability prevents you from using the same one in two separate AppDomains anyway.
Standard caveats about minimal error-checking,
Dispose
implementations, etc.