命名空間:System.Runtime.Remoting.Messaging
類型完全限定名稱:System.Runtime.Remoting.Messaging.CallContext
用途:
用於提供與執行代碼路徑一起傳送的屬性集,直白講就是:提供線程(多線程/單線程)代碼執行路徑中數據傳遞的能力。
方法 | 描述 | 是否可用於多線程環境 |
SetData | 存儲給定的對象並將其與指定名稱關聯。 | 否 |
GetData | 從System.Runtime.Remoting.Messaging.CallContext中檢索具有指定名稱的對象 | 否 |
LogicalSetData | 將給定的對象存儲在邏輯調用上下文,並將其與指定名稱關聯。 | 是 |
LogicalGetData | 從邏輯調用上下文中檢索具有指定名稱的對象。 | 是 |
FreeNamedDataSlot | 清空具有指定名稱的數據槽。 | 是 |
HostContext | 獲取或設置與當前線程相關聯的主機上下文。在Web環境下等於System.Web.HttpContext.Current | 否 |
為了更加明確的認識這些方法的作用以及作用我們通過以下代碼來了解:
首先定義一個類:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
一、單線/多線程環境,測試GetData、SetData、FreeNamedDataSlot
下邊的輸出分別對應1、2、3的輸出
根據上述測試結果我們基本可以得出以下結論:
1、GetData、SetData只能用於單線程環境,如果發生了線程切換,存儲的數據也會隨之丟失
2、可以用於同一線程中的不同地方,傳遞數據
一、單線/多線程環境,測試LogicalSetData、LogicalGetData、FreeNamedDataSlot
通過上述測試可得出結論:
a、FreeNamedDataSlot只能清除當前線程的數據槽,不能清除子線程的數據槽;
b、LogicalSetData、LogicalGetData可用於在多線程環境下傳遞數據;
c、FreeNamedDataSlot清除當前線程,之前已經運行子任務,不受影響
通過與上一測試對比我們可以得出結論:
a、FreeNamedDataSlot只能清除當前線程的數據槽
b、LogicalSetData只是存儲當前線程以及子線程的數據槽
c、LogicalGetData獲取的是當前線程或父線程的數據槽對象,拿到的時對象的引用
d、子線程中使用LogicalSetData改變數據槽的值,不能印象父線程的數據槽,及時他們的key時一個
現在清楚了它的功能,這樣我們就可以來思考它的使用場景。
1、可以解耦代碼,以前我們向下傳遞數據是通過參數變量的形式,方法嵌套,我么就需要額外的參數一層一層傳遞。如果我們Get了CallContext技能,就可以對代碼進行解耦;
2、工作單元(UOW),像XPO的工作單元以及ABP的工作單元,都是通過CallContext來實現的,ABP詳見IUnitOfWorkManager.Current->ICurrentUnitOfWorkProvider.Current->CallContextCurrentUnitOfWorkProvider.Current
3、 System.Web.HttpContext.Current我們常用來獲取當前請求上下文,使用的是System.Runtime.Remoting.Messaging.CallContext.HostContext
System.Runtime.Remoting.Messaging.CallContext.HostContext get訪問器和set訪問器實現和GetData和SetData的實現方式一致
在.NET Core中使用AsyncLocal代替CallContext,實現的是CallContext.LogicalGetData 和CallContext.SetLogicalCallContext
此處需要重點說下ILogicalThreadAffinative,其作用是:將一個對象,可以將外部傳播標記 System.AppDomain 中 System.Runtime.Remoting.Messaging.LogicalCallContext。
如果需要存在在執行上下文中的對象繼承了ILogicalThreadAffinative接口,使用LogicalSetData/LogicalGetData和SetData/GetData作用是一樣的。請看以下源碼截圖:
[SecurityCritical] public static void SetData(string name, object data) { if (data is ILogicalThreadAffinative) { LogicalSetData(name, data); } else { ExecutionContext mutableExecutionContext = Thread.CurrentThread.GetMutableExecutionContext(); mutableExecutionContext.LogicalCallContext.FreeNamedDataSlot(name); mutableExecutionContext.IllogicalCallContext.SetData(name, data); } }
[SecurityCritical] public static object GetData(string name) { object obj2 = LogicalGetData(name); return ((obj2 != null) ? obj2 : IllogicalGetData(name)); }
我們可以看到,不管你是使用LogicalSetData還是SetData存儲的數據,我們使用GetData都是可以取到的。如果SetData的值是繼承ILogicalThreadAffinative接口,那不管使用那個都是一樣的效果;
實際上在使用System.Web.HttpContext.Current是通過CallContext.HostContext實現的,在看其源碼時發現一個很不理解的現象
public static object HostContext { [SecurityCritical] get { ExecutionContext.Reader executionContextReader = Thread.CurrentThread.GetExecutionContextReader(); object hostContext = executionContextReader.IllogicalCallContext.HostContext; if (hostContext == null) { hostContext = executionContextReader.LogicalCallContext.HostContext;//這里又為什么取,不是無用功么 } return hostContext; } [SecurityCritical] set { ExecutionContext mutableExecutionContext = Thread.CurrentThread.GetMutableExecutionContext(); if (value is ILogicalThreadAffinative) { mutableExecutionContext.IllogicalCallContext.HostContext = null; mutableExecutionContext.LogicalCallContext.HostContext = value; } else { mutableExecutionContext.IllogicalCallContext.HostContext = value; mutableExecutionContext.LogicalCallContext.HostContext = null;//此處為什么要設置為null } } }
仔細看了下,取決於
ILogicalThreadAffinative,要不要傳播,以此減少一次存儲。