2013-03-01 13 views
12

Ho un oggetto comando, che lavora in base a una richiesta da una coda di richieste. Questo particolare comando eseguirà il suo lavoro in un appdomain secondario. Parte del suo lavoro nell'appadominio figlio comporta il blocco su un'operazione ConcurrentQueue (ad esempio, Aggiungi o Prendi). Devo essere in grado di propagare un segnale di interruzione attraverso la coda di richiesta, attraverso il dominio secondario, e di risvegliare i thread di lavoro al suo interno.Come posso passare CancellationToken attraverso il confine AppDomain?

Pertanto, penso di dover passare un CancellationToken attraverso il confine AppDomain.

Ho cercato di creare una classe che eredita da 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; 
     } 

    }; 

e passare questo come un argomento della funzione operaio:

// 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); 

Ma ho ancora un'eccezione quando l'objectInRemoteAppDomain cerca di accedere alla proprietà getter Token:

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. 

La mia domanda è: Come posso propagare il segnale di interruzione/annullamento attraverso le appdomain e attivare i thread che possono essere bloccati nelle strutture di dati di concorrenza .NET (dove sono supportati gli argomenti di CancellazioneToken).

risposta

19

È passato un po 'di tempo dall'ultima volta che ho guardato a qualcosa di cross-AppDomain, quindi potrebbero esserci problemi con questo codice che non ho realizzato, ma sembra che faccia il lavoro. Il problema fondamentale è che non sembra esserci modo di trasferire una Fonte annullata [Fonte] da un AppDomain a un altro. Quindi creo due sorgenti, con la configurazione primaria per cancellare il secondario quando appropriato.

Il fatto che in questo scenario ci siano sia due fonti di token separati in questo scenario potrebbe essere un problema, ma non penso che tu stia smarrendo il fatto che la mancanza di serializzazione ti impedisce di usare lo stesso in due AppDomain separati comunque.

avvertimenti di serie su minimo controllo degli errori, Dispose implementazioni, ecc

// 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

Questa è la soluzione perfetta per quanto posso dire. – usr

0

v'è in realtà un modo molto più semplice per superare questo ostacolo supponendo che il tipo di proxy è un singolo responsabilità. Presumo, naturalmente, che tu mantenga una collezione dei tuoi domini creati e li scarichi in caso di chiusura della tua applicazione o di eliminazione dell'oggetto contenuto. Presumo inoltre che il motivo per cui è necessario il token di annullamento è quello di annullare alcune operazioni asincrone nel tipo di riferimento sottoposto a marshalling. È sufficiente effettuare le seguenti operazioni:

Creare i campi token Source e token e inizializzarli nel costruttore.

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

Iscriviti ai seguenti eventi. L'UnhandledException servirà allo scopo di rilevare eventuali eccezioni che causano la chiusura prematura del dominio. Questa dovrebbe essere una buona pratica.

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

Chiama annulla sull'origine del token quando viene chiamato l'evento di scaricamento del dominio. Inoltre, è possibile che si desideri disporre di un metodo di eliminazione che annulli l'iscrizione agli eventi del dominio che vengono richiamati o che consenta solo alla procedura di pulizia del dominio di eseguire la 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

Intelligente. Molto bella. – gap

Problemi correlati