C#使用CallContext緩存線程數據
一、CallContext 概述
命名空間:System.Runtime.Remoting.Messaging
CallContext 用於提供與執行代碼路徑一起傳送的屬性集,直白講就是:提供線程(多線程/單線程)代碼執行路徑中數據傳遞的能力。
當對另一個 AppDomain 中的對象進行遠程方法調用時,CallContext 類將生成一個與該遠程調用一起傳播的 LogicalCallContext 實例。隻有公開 ILogicalThreadAffinative 接口並存儲在 CallContext 中的對象被在 LogicalCallContext 中傳播到 AppDomain 外部。
CallContext成員
- SetData: 存儲給定的對象並將其與指定名稱關聯。
- GetData: 從CallContext中檢索具有指定名稱的對象
- LogicalSetData: 將給定的對象存儲在邏輯調用上下文,並將其與指定名稱關聯。可用於多線程環境
- LogicalGetData: 從邏輯調用上下文中檢索具有指定名稱的對象。可用於多線程環境
- FreeNamedDataSlot: 清空具有指定名稱的數據槽。可用於多線程環境
- HostContext屬性: 獲取或設置與當前線程相關聯的主機上下文。在Web環境下等於System.Web.HttpContext.Current
GetData、SetData
-
隻能用於單線程環境,如果發生瞭線程切換,存儲的數據也會隨之丟失
-
可以用於同一線程中的不同地方,傳遞數據
LogicalSetData、LogicalGetData
- LogicalSetData、LogicalGetData可用於在多線程環境下傳遞數據;
- LogicalSetData隻是存儲當前線程以及子線程的數據槽
- LogicalGetData獲取的是當前線程或父線程的數據槽對象,拿到的是對象的引用
- FreeNamedDataSlot清除當前線程,之前已經運行子任務,不受影響,不能清除子線程的數據槽;
二、 CallContext不跨線程傳播的方法:GetData、SetData
可以利用CallContext 實現單例,默認情況下,CallContext 的數據不跨線程傳播。
1、在處理多組件共用Context時非常有用,比如常見的EF 可以將實例的DBEntity存儲在其中,可以一次訪問隻實例化一次,便於管理且不用多次實例訪問對象
public static class DbContextHelper { private static DbContext context = null; private const string SessionKey_DbContext = "Entities"; public static DbContext GetDbContext() { if (CallContext.GetData(SessionKey_DbContext) == null) { CallContext.SetData(SessionKey_DbContext, new Entities()); } return CallContext.GetData(SessionKey_DbContext) as Entities; } }
2、類單例
void Main() { MyAppContext.Current.FirstName = "a"; Console.Write(MyAppContext.Current.FirstName); } public class MyAppContext { const string contextKey = "MyAppContext:ContextKey"; public string FirstName { get; set; } public static MyAppContext Current { get { if (CallContext.GetData(contextKey) == null) { CallContext.SetData(contextKey, new MyAppContext()); } return CallContext.GetData(SessionKey_DbContext) as MyAppContext; } } }
三、 CallContext跨線程傳播的方法:ILogicalSetData、LogicalGetData
要讓CallContext實現跨線程傳播,可以調用CallContext的靜態方法ILogicalSetData,或讓上下文類實現ILogicalThreadAffinative 接口。
線程本地存儲
線程池可能不會釋放使用過的線程,導致多次執行之間可能共享數據(可以每次執行前重置線程本地存儲的數據)。
for (var i = 0; i < 10; i++) { Thread.Sleep(10); Task.Run(() => { var slot = Thread.GetNamedDataSlot("test"); if (slot == null) { Thread.AllocateNamedDataSlot("test"); } if (Thread.GetData(slot) == null) { Thread.SetData(slot, DateTime.Now.Millisecond); } Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + Thread.GetData(slot)); }); }
結果
調用上下文
每次執行的數據是完全隔離的,非常符合我們的期望。但是,如果我們期望調用期間又開啟瞭一個子線程,如何讓子線程訪問父線程的數據呢?這就需要使用到:“邏輯調用上下文”。
Console.WriteLine("測試:CallContext.SetData"); for (var i = 0; i < 10; i++) { Thread.Sleep(10); Task.Run(() => { if (CallContext.GetData("test") == null) { CallContext.SetData("test", DateTime.Now.Millisecond); } Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test")); }); }
結果
每次執行的數據是完全隔離的,非常符合我們的期望。
邏輯調用上下文
如果我們期望調用期間又開啟瞭一個子線程,如何讓子線程訪問父線程的數據呢?這就需要使用到:“邏輯調用上下文”。
註意 ExecutionContext.SuppressFlow(); 和ExecutionContext.RestoreFlow();它們分別能阻止傳播和重置傳播,默認是允許傳播的。
Console.WriteLine("測試:CallContext.SetData"); Task.Run(() => { CallContext.SetData("test", "段光偉"); Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test")); Task.Run(() => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test")); }); }); Thread.Sleep(100); Console.WriteLine("測試:CallContext.LogicalSetData"); Task.Run(() => { CallContext.LogicalSetData("test", "段光偉"); Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.LogicalGetData("test")); Task.Run(() => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.LogicalGetData("test")); }); ExecutionContext.SuppressFlow(); Task.Run(() => { Console.WriteLine("SuppressFlow 之後:" + CallContext.LogicalGetData("test")); }); ExecutionContext.RestoreFlow(); Task.Run(() => { Console.WriteLine("RestoreFlow 之後:" + CallContext.LogicalGetData("test")); }); });
輸出
四、Web中的CallContext
HttpContext.Current(包括Session)的存儲是基於當前線程的CallContext,在非請求處理線程(即其他線程)是無法獲取當前HttpContext的(不跨線程傳播)。
到此這篇關於C#使用CallContext緩存線程數據的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- C# 異步多線程入門基礎
- C# 異步多線程入門到精通之Thread篇
- 深入瞭解C#多線程安全
- .NET實現異步編程async和await
- c# Task.Wait()與awaiat Task異常處理的區別說明