2010-04-16 116 views
3

我一直都嵌套多線程操作跟蹤

void ExecuteTraced(Action a, string message) 
{ 
    TraceOpStart(message); 
    a(); 
    TraceOpEnd(message); 
} 

回調(一)可能會再次打電話ExecuteTraced,並且,在某些情況下,異步(通過線程池,BeginInvoke的,PLINQ等,所以我有一個代碼沒有明確標示操作範圍的能力)。我想跟蹤所有嵌套操作(即使它們是異步執行的)。所以,我需要能夠在邏輯調用上下文中獲得最後的跟蹤操作(可能會有很多併發線程,因此不可能使用lastTraced靜態字段)。

有CallContext.LogicalGetData和CallContext.LogicalSetData,但不幸的是,LogicalCallContext作爲EndInvoke()調用傳播更改回父上下文。更糟糕的是,如果EndInvoke()被稱爲異步,可能會在任何時候發生。 EndInvoke changes current CallContext - why?

此外,還有Trace.CorrelationManager,但它基於CallContext並具有所有相同的麻煩。

有一種解決方法:使用CallContext.HostContext屬性,該屬性在異步操作結束時不會傳回。此外,它不克隆,所以值應該是不可變的 - 不是問題。雖然它被HttpContext使用,所以,解決方法在Asp.Net應用程序中不可用。

我看到的唯一方法是將HostContext(如果不是我的)或整個LogicalCallContext包裝爲動態並調度旁邊的最後一個跟蹤操作的所有調用。

回答

6

好吧,我在回答我自己。

短一:有沒有解決方案。

稍微詳細:

的問題是,我需要一種方法來最後主動操作每個存儲每個邏輯背景。跟蹤代碼將無法控制執行流程,因此不可能將lastStartedOperation作爲參數傳遞。調用上下文可能會克隆(例如,如果另一個線程啓動了),所以我需要克隆作爲上下文克隆的值。 CallContext.LogicalSetData()很適合,但它在異步操作結束時將值合併到原始上下文中(實際上,替換在調用EndInvoke之前所做的所有更改)。理論上,它可能會異步地發生,從而導致CallContext.LogicalGetData()的結果不可預知。

我說理論上是因爲asyncCallback中的簡單調用a.EndInvoke()不會替換原始上下文中的值。雖然,我沒有檢查遠程調用的行爲(看來,WCF根本不尊重CallContext)。此外,documentation(舊)說:

BeginInvoke方法傳遞 CallContext中的服務器。當調用 EndInvoke方法時, CallContext被合併回 線程。這包括其中 BeginInvoke和EndInvoke依次調用 和的情況,其中BeginInvoke是 在一個線程上調用,EndInvoke是 在回調函數上調用。

最後版本沒有那麼明確的:

BeginInvoke方法傳遞 CallContext中的服務器。當調用 EndInvoke方法時,CallContext中包含的數據 被複製 回到調用 BeginInvoke的線程上。

如果你到Digg的框架源代碼,你會發現,值實際存儲在當前線程的當前執行上下文內內LogicalCallContext一個哈希表內。

當調用上下文克隆(例如在BeginInvoke)上調用LogicalCallContext.Clone時。 EndInvoke(至少在原始CallContext中調用時)會調用LogicalCallContext.Merge()替換m_Datastore中的舊值。

所以我們需要以某種方式提供將被克隆但未合併的值。

LogicalCallContext.Clone()還克隆(不合並)兩個專用字段m_RemotingData和m_SecurityData的內容。由於該字段的類型定義爲internal,因此無法從它們派生出來(即使使用emit),請添加屬性MyNoFlowbackValue,並將m_RemotingData(或另一個)字段的值替換爲派生類的實例。

此外,字段的類型不是從MBR派生的,因此不可能使用透明代理來包裝它們。

你不能從LogicalCallContext繼承 - 它是密封的。 (注意事實上,你可以 - 如果使用CLR分析API來替代IL,如同模擬框架一樣,不是理想的解決方案。)

你不能替換m_Datastore值,因爲LogicalCallContext僅序列化哈希表的內容,而不是哈希表本身。

最後的解決方案是使用CallContext.HostContext。這有效地將數據存儲在LogicalCallContext的m_hostContext字段中。 LogicalCallContext.Clone()共享(不克隆)m_hostContext的值,因此該值應該是不可變的。雖然不是問題。

即使使用HttpContext也會失敗,因爲它將CallContext.HostContext屬性設置爲替換舊值。具有諷刺意味的是,HttpContext沒有實現ILogicalThreadAffinative,因此不存儲爲m_hostContext字段的值。它只是用null替換舊值。

因此,沒有解決方案,永遠不會,因爲CallContext是遠程處理的一部分,遠程處理已經過時。

P.S. Thace.CorrelationManager在內部使用CallContext,因此也不能按需要工作。順便說一下,LogicalCallContext有一個特殊的解決方法來克隆上下文克隆上的CorrelationManager的操作棧。可悲的是,它沒有專門的合併解決方法。完善!

P.P.S.示例:

static void Main(string[] args) 
{ 
    string key = "aaa"; 
    EventWaitHandle asyncStarted = new AutoResetEvent(false); 
    IAsyncResult r = null; 

    CallContext.LogicalSetData(key, "Root - op 0"); 
    Console.WriteLine("Initial: {0}", CallContext.LogicalGetData(key)); 

    Action a =() => 
    { 
     CallContext.LogicalSetData(key, "Async - op 0"); 
     asyncStarted.Set(); 
    }; 
    r = a.BeginInvoke(null, null); 

    asyncStarted.WaitOne(); 
    Console.WriteLine("AsyncOp started: {0}", CallContext.LogicalGetData(key)); 

    CallContext.LogicalSetData(key, "Root - op 1"); 
    Console.WriteLine("Current changed: {0}", CallContext.LogicalGetData(key)); 

    a.EndInvoke(r); 
    Console.WriteLine("Async ended: {0}", CallContext.LogicalGetData(key)); 

    Console.ReadKey(); 
} 
+1

感謝您提供非常詳細的資料,對我來說非常有用的信息,而且我認識到它很難找到!感謝您發佈它 – 2012-08-10 17:27:38