2013-03-01 76 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

相關問題