日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

.NET | 多线程下的调用上下文 : CallContext

發布時間:2023/12/4 asp.net 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .NET | 多线程下的调用上下文 : CallContext 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【.NET| 總結/Edison Zhou


最近在分析現在團隊的項目代碼(基于.NET Framework 4.5),經常發現一個CallContext的調用,記得多年前的時候用到了它,但是印象已經不深刻了,于是現在來復習一下。

1CallContext是個啥?

如果說,一個對象保證全局唯一,大家肯定會想到一個經典的設計模式:單例模式。但是,如果要使用的對象必須是線程內唯一的呢?

在.NET Framework中,Microsoft給我們設計了一個CallContext類。

  • 命名空間:System.Runtime.Remoting.Messaging

  • 類型完全限定名稱:System.Runtime.Remoting.Messaging.CallContext

CallContext類似于方法調用的線程本地存儲區的專用集合對象,并提供對每個邏輯執行線程都唯一的數據槽。數據槽不在其他邏輯線程上的調用上下文之間共享。當 CallContext 沿執行代碼路徑往返傳播并且由該路徑中的各個對象檢查時,可將對象添加到其中。

簡而言之,CallContext提供線程(多線程/單線程)代碼執行路徑中數據傳遞的能力。

方法

描述

線程安全

SetData

存儲給定的對象并將其與指定名稱關聯。

GetData

從System.Runtime.Remoting.Messaging.CallContext中檢索具有指定名稱的對象

LogicalSetData

將給定的對象存儲在邏輯調用上下文,并將其與指定名稱關聯。

LogicalGetData

?從邏輯調用上下文中檢索具有指定名稱的對象。

FreeNamedDataSlot

清空具有指定名稱的數據槽。

HostContext

?獲取或設置與當前線程相關聯的主機上下文。在Web環境下等于System.Web.HttpContext.Current


2探究CallContext方法

上面介紹了CallContext提供的核心方法,下面我們就來通過實踐來理解一下。

準備工作

這里準備一個User類作為數據傳遞對象:

public class User {public string Id { get; set; }public string Name { get; set; } }

測試1:GetData、SetData 與 FreeNamedDataSlot

測試代碼很簡單,就是在主線程 和 子線程之中分別傳遞User對象實例,看看最后的效果。

public?void?TestGetSetData() {// 主線程執行Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");var user = new User(){Id = DateTime.Now.ToString(),Name = "Edison"};CallContext.SetData("key", user);var value1 = CallContext.GetData("key");Console.WriteLine(user == value1);// 異步線程執行Task.Run(() =>{Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");var value2 = CallContext.GetData("key");Console.WriteLine(value2 == null ?"NULL" : (value2 == value1).ToString());});// 主線程執行Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");value1 = CallContext.GetData("key");Console.WriteLine(value1 == user);// 清理數據槽CallContext.FreeNamedDataSlot("key");var value3 = CallContext.GetData("key");Console.WriteLine(value3 == null ?"NULL" : (value3 == value1).ToString()); }

上面示例代碼的運行結果如下圖所示:

根據上圖所示的結果,基本可以得出以下兩個結論:

1、GetData、SetData方法只能用于單線程環境,如果發生了線程切換,存儲的數據也會隨之丟失。

2、GetData 和 SetData 可以用于同一線程中的不同地方,傳遞數據

可以知道,要在多線程環境下使用,我們需要用到另外兩個方法:LogicalSetData 與 LogicalGetData。

測試2:LogicalGetData、LogicalSetData 與 FreeNamedDataSlot

public void TestLogicalGetSetData() {// 主線程執行Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");var user = new User(){Id = DateTime.Now.ToString(),Name = "Edison"};CallContext.LogicalSetData("key", user);var value1 = CallContext.LogicalGetData("key");Console.WriteLine(user == value1);// 異步線程執行Task.Run(() =>{Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");var value2 = CallContext.LogicalGetData("key");Console.WriteLine(value2 == null ?"NULL" : (value2 == value1).ToString());Thread.Sleep(1000);value2 = CallContext.LogicalGetData("key");Console.WriteLine(value2 == null ?"NULL" : (value2 == value1).ToString());});// 主線程執行Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");// 清理數據槽CallContext.FreeNamedDataSlot("key");var value3 = CallContext.LogicalGetData("key");Console.WriteLine(value3 == null ?"NULL" : (value3 == value1).ToString()); }

這段示例代碼的運行結果如下圖所示:

根據上圖所示的結果,基本可以得出以下三個結論:

1、FreeNamedDataSlot只能清除當前線程的數據槽,不能清除子線程的數據槽;

2、LogicalSetData、LogicalGetData可用于在多線程環境下傳遞數據

3、FreeNamedDataSlot清除當前線程的數據槽后,之前已經運行的子任務,不受影響

測試3:LogicalGetData后修改傳遞的數據

在多線程環境下傳遞共享對象數據,如果某個線程通過LogicalGetData后對其進行了修改又重新LogicalSetData會怎樣?

public void TestLogicalGetSetDataV2() {// 主線程執行Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");var user = new User(){Id = DateTime.Now.ToString(),Name = "Edison"};CallContext.LogicalSetData("key", user);var value1 = CallContext.LogicalGetData("key");Console.WriteLine(user == value1);// 異步線程同步執行:加了.Wait()Task.Run(() =>{Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");var value2 = CallContext.LogicalGetData("key");Console.WriteLine(value2 == null ?"NULL" : (value2 == value1).ToString());CallContext.FreeNamedDataSlot("key");value2 = CallContext.LogicalGetData("key");Console.WriteLine(value2 == null ?"NULL" : (value2 == value1).ToString());}).Wait();// 異步線程同步執行:加了.Wait()Task.Run(() =>{Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");var value2 = CallContext.LogicalGetData("key") as User;Console.WriteLine(value2 == null ?"NULL" : (value2 == value1).ToString());value2.Name?=?"Leo";CallContext.LogicalSetData("key", new User() { Id = DateTime.Now.ToString(), Name = "Jack" }); // 只影響當前線程value2 = CallContext.LogicalGetData("key") as User;Console.WriteLine(value2 == null ?"NULL" : (value2 == value1).ToString());Console.WriteLine($"User.Name={value2.Name}");}).Wait();// 主線程執行Console.WriteLine($"Current ThreadId={Thread.CurrentThread.ManagedThreadId}");var value3 = CallContext.LogicalGetData("key") as User;Console.WriteLine(value3 == null ?"NULL" : (value3 == value1).ToString());Console.WriteLine($"User.Name={value3.Name}"); }

上面示例代碼的運行結果如下圖所示:

根據上面的示例運行結果,我們又可以得到以下一些結論:

1、FreeNamedDataSlot只能清除當前線程的數據槽

2、LogicalSetData只會存儲當前線程以及子線程的數據槽

3、LogicalGetData獲取的是當前線程或父線程的數據槽對象,拿到的是對象的引用,因此如果對其進行修改,會影響父線程讀取的一致性,在關系型數據庫中也被稱為不可重復讀。

4、子線程中使用LogicalSetData改變數據槽的值,不會影響父線程的數據槽,即使他們的key是同一個

3.NET Core下沒有CallContext

在.NET Core下沒有CallContext類,取而代之的是使用AsyncLocal代替,實現的是CallContext.LogicalGetData 和 CallContext.SetLogicalCallContext。

例如,下面是一個示例代碼,我們可以借助AsyncLocal來自己實現一個CallContext類。如果你是將.NET Framework升級為.NET Core,那么你可能需要自己實現一個CallContext類來代替之前的CallContext:

public static class CallContext {static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>();public static void SetData(string name, object data) =>state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;public static object GetData(string name) =>state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null; }

4EF DbContext場景

對于像UnitOfWork這種操作模式,是比較適合于CallContext發揮的地方,讓EF DbContext在線程上下文內保持唯一。

注意:這里提到的EF均指EF 而非 EF Core。

因此,我們經常可以看到如下所示的示例代碼:

public class DbContextFactory {public static DbContext CreateDbContext(){DbContext dbContext = (DbContext)CallContext.GetData("dbContext");if (dbContext == null){dbContext = new WebAppEntities();CallContext.SetData("dbContext", dbContext);}return dbContext;} }

此用法像極了 Cache(緩存)的使用。

But,鑒于目前廣泛使用線程池的前提,線程在處理完一個請求之后,并沒有被銷毀,存儲在CallContext中的上下文對象也一直存在,如果是下一次拿出這個線程去處理另一個請求,這個上下文對象其實也在不斷的膨脹,只不過比全局的膨脹的稍微慢一些。而且,有時候一個線程并不一定是拿去處理請求了,如果是服務器拿去處理其他的業務,那就可能引發一些其他的問題。

這時,或許我們可以考慮另一個方案,在ASP.NET中的HttpContext中有一個Items屬性,它也可以用來保存key-value,這就完美了,一次請求正好對應著一個HttpContext,請求結束,它自動釋放,EF上下文也就不存在了。

因此,這里把上面代碼中的CallContext改為HttpContext.Current.Items:

public class DbContextFactory {public static DbContext CreateDbContext(){DbContext dbContext = HttpContext.Current.Items["dbContext"] as DbContext;if (dbContext == null){dbContext = new WebAppEntities();HttpContext.Current.Items["dbContext"]?=?dbContext;}return dbContext;} }

其實,HttpContext這個類和CallContext是有關聯的,查看源碼我們可以發現:HttpContext.Current是通過CallContext.HostContext實現的。

internal static Object Current {get {return CallContext.HostContext;}[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]set {CallContext.HostContext = value;} }

關于HttpContext.Current:ASP.NET會為每個請求分配一個線程,這個線程會執行我們的代碼來生成響應結果, 即使我們的代碼散落在不同的地方(類庫),線程仍然會執行它們。所以,我們可以在任何地方訪問HttpContext.Current獲取到與當前請求相關的HttpContext對象,畢竟這些代碼是由同一個線程來執行的嘛,所以得到的HttpContext引用也就是那個與請求相關的對象。因此,將HttpContext.Current設計成與當前線程相關聯是合適的。有關CallContext.HostContext的知識可以自行查閱資料,這里就不再贅述。

剛剛提到UnitOfWork模式,我們完成了DbContext的線程上下文內的唯一性,那么SaveChanges呢?嗯,我們可以基于之前的唯一性保證,來寫一個SaveChanges的唯一入口。

public?class?DbSession {public static int SaveChanges(){return DbContextFactory.GetDbContext().SaveChanges();} }

End總結

本文簡單介紹了CallContext類的基本概念、方法,做了一些測試驗證了其提供的方法的適用范圍和限制。

如果我們需要在.NET代碼中向下傳遞對象,除了層層遞進的傳遞參數之外,適時使用CallContext是一個不錯的解耦的方案。

參考資料

Microsoft Doc,CallContext

.NET源碼,https://referencesource.microsoft.com/#System.Web/HttpContext.cs

雯海,.NET多線程之CallContext(cnblogs博客)

Koma,EF上下文對象線程內唯一性與優化(csdn博客)

年終總結:Edison的2020年終總結

數字化轉型:我在傳統企業做數字化轉型

C#刷題:C#刷劍指Offer算法題系列文章目錄

.NET面試:.NET開發面試知識體系

.NET大會:2020年中國.NET開發者大會PDF資料

總結

以上是生活随笔為你收集整理的.NET | 多线程下的调用上下文 : CallContext的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。