How do I pass CancellationToken across AppDomain boundary?

后端 未结 2 1304
刺人心
刺人心 2021-02-02 16:04

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 ch

相关标签:
2条回答
  • 2021-02-02 16:42

    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.

    // 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.
    }
    
    0 讨论(0)
  • 2021-02-02 16:43

    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.

    _cancellationTokenSource = new CancellationTokenSource();
    _token = _cancellationTokenSource.Token;
    

    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.

    var currDomain = AppDomain.CurrentDomain;
                currDomain.DomainUnload += currDomain_DomainUnload;
                currDomain.UnhandledException += currDomain_UnhandledException;
    

    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.

    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 讨论(0)
提交回复
热议问题