C#使用CallContext緩存線程數(shù)據(jù)
一、CallContext 概述
命名空間:System.Runtime.Remoting.Messaging
CallContext 用于提供與執(zhí)行代碼路徑一起傳送的屬性集,直白講就是:提供線程(多線程/單線程)代碼執(zhí)行路徑中數(shù)據(jù)傳遞的能力。
當對另一個 AppDomain 中的對象進行遠程方法調用時,CallContext 類將生成一個與該遠程調用一起傳播的 LogicalCallContext 實例。只有公開 ILogicalThreadAffinative 接口并存儲在 CallContext 中的對象被在 LogicalCallContext 中傳播到 AppDomain 外部。
CallContext成員
- SetData: 存儲給定的對象并將其與指定名稱關聯(lián)。
- GetData: 從CallContext中檢索具有指定名稱的對象
- LogicalSetData: 將給定的對象存儲在邏輯調用上下文,并將其與指定名稱關聯(lián)。可用于多線程環(huán)境
- LogicalGetData: 從邏輯調用上下文中檢索具有指定名稱的對象??捎糜诙嗑€程環(huán)境
- FreeNamedDataSlot: 清空具有指定名稱的數(shù)據(jù)槽??捎糜诙嗑€程環(huán)境
- HostContext屬性: 獲取或設置與當前線程相關聯(lián)的主機上下文。在Web環(huán)境下等于System.Web.HttpContext.Current
GetData、SetData
只能用于單線程環(huán)境,如果發(fā)生了線程切換,存儲的數(shù)據(jù)也會隨之丟失
可以用于同一線程中的不同地方,傳遞數(shù)據(jù)
LogicalSetData、LogicalGetData
- LogicalSetData、LogicalGetData可用于在多線程環(huán)境下傳遞數(shù)據(jù);
- LogicalSetData只是存儲當前線程以及子線程的數(shù)據(jù)槽
- LogicalGetData獲取的是當前線程或父線程的數(shù)據(jù)槽對象,拿到的是對象的引用
- FreeNamedDataSlot清除當前線程,之前已經(jīng)運行子任務,不受影響,不能清除子線程的數(shù)據(jù)槽;
二、 CallContext不跨線程傳播的方法:GetData、SetData
可以利用CallContext 實現(xiàn)單例,默認情況下,CallContext 的數(shù)據(jù)不跨線程傳播。
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實現(xiàn)跨線程傳播,可以調用CallContext的靜態(tài)方法ILogicalSetData,或讓上下文類實現(xiàn)ILogicalThreadAffinative 接口。
線程本地存儲
線程池可能不會釋放使用過的線程,導致多次執(zhí)行之間可能共享數(shù)據(jù)(可以每次執(zhí)行前重置線程本地存儲的數(shù)據(jù))。
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));
});
}結果

調用上下文
每次執(zhí)行的數(shù)據(jù)是完全隔離的,非常符合我們的期望。但是,如果我們期望調用期間又開啟了一個子線程,如何讓子線程訪問父線程的數(shù)據(jù)呢?這就需要使用到:“邏輯調用上下文”。
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"));
});
}結果

每次執(zhí)行的數(shù)據(jù)是完全隔離的,非常符合我們的期望。
邏輯調用上下文
如果我們期望調用期間又開啟了一個子線程,如何讓子線程訪問父線程的數(shù)據(jù)呢?這就需要使用到:“邏輯調用上下文”。
注意 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緩存線程數(shù)據(jù)的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
分享兩種實現(xiàn)Winform程序的多語言支持的多種解決方案
本篇文章主要介紹了分享兩種實現(xiàn)Winform程序的多語言支持的多種解決方案,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧。2017-02-02
WPF/Silverlight實現(xiàn)圖片局部放大的方法分析
這篇文章主要介紹了WPF/Silverlight實現(xiàn)圖片局部放大的方法,結合實例形式分析了WPF/Silverlight針對圖片屬性操作相關實現(xiàn)技巧,需要的朋友可以參考下2017-03-03

