亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺析C#?AsyncLocal如何實現(xiàn)Thread間傳值

 更新時間:2024年01月26日 08:28:04   作者:一線碼農  
這篇文章主要是來和大家一起討論一下C#?AsyncLocal如何實現(xiàn)Thread間傳值,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下

一:背景

講故事

這個問題的由來是在.NET高級調試訓練營第十期分享ThreadStatic底層玩法的時候,有朋友提出了AsyncLocal是如何實現(xiàn)的,雖然做了口頭上的表述,但總還是會不具體,所以覺得有必要用文字+圖表的方式來系統(tǒng)的說一下這個問題。

二:AsyncLocal 線程間傳值

1. 線程間傳值途徑

在 C# 編程中實現(xiàn)多線程以及線程切換的方式大概如下三種:

  • Thread
  • Task
  • await,async

這三種場景下的線程間傳值有各自的實現(xiàn)方式,由于篇幅限制,先從 Thread 開始聊吧。本質上來說 AsyncLocal 是一個純托管的C#玩法,和 coreclr,Windows 沒有任何關系。

2. Thread 小例子

為了方便講述,先來一個例子看下如何在新Thread線程中提取 _asyncLocal 中的值,參考代碼如下:

    internal class Program
    {
        static AsyncLocal<int> _asyncLocal = new AsyncLocal<int>();

        static void Main(string[] args)
        {
            _asyncLocal.Value = 10;

            var t = new Thread(() =>
            {
                Console.WriteLine($"Tid={Thread.CurrentThread.ManagedThreadId}, AsyncLocal value: {_asyncLocal.Value},");
                Debugger.Break();
            });

            t.Start();

            Console.ReadLine();
        }
    }

從截圖看 tid=7 線程果然拿到了 主線程設置的 10 ,哈哈,是不是充滿了好奇心?接下來逐一分析下吧。

3. 流轉分析

首先觀察下 _asyncLocal.Value = 10 在源碼層做了什么,參考代碼如下:

    public T Value
    {
        set
        {
            ExecutionContext.SetLocalValue(this, value, m_valueChangedHandler != null);
        }
    }

    internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications)
    {
        ExecutionContext executionContext = Thread.CurrentThread._executionContext;

        Thread.CurrentThread._executionContext = new ExecutionContext(asyncLocalValueMap, array, flag2));
    }

從源碼中可以看到這個 10 最終封印在 Thread.CurrentThread._executionContext 字段中,接下來就是核心問題了,它是如何被送到新線程中的呢?

其實仔細想一想,要讓我實現(xiàn)的話,我肯定這么實現(xiàn)。

  • 將主線程的 _executionContext 字段賦值給新線程 t._executionContext 字段。
  • 將 var t = new Thread() 中的t作為參數(shù)傳遞給 win32 的 CreateThread 函數(shù),這樣在新線程中就可以提取 到 t 了,然后執(zhí)行 t 的callback。

這么說大家可能有點抽象,我就直接畫下C#是怎么流轉的圖吧:

有了這張圖之后接下來的問題就是驗證了,首先看一下 copy 操作在哪里? 可以觀察下 Start 源碼。

    private void Start(bool captureContext)
    {
        StartHelper startHelper = _startHelper;
        if (startHelper != null)
        {
            startHelper._startArg = null;
            startHelper._executionContext = (captureContext ? System.Threading.ExecutionContext.Capture() : null);
        }
        StartCore();
    }
    public static ExecutionContext? Capture()
    {
        ExecutionContext executionContext = Thread.CurrentThread._executionContext;
        return executionContext;
    }

從源碼中可以看到將主線程的 _executionContext 字段給了新線程t下的startHelper._executionContext 。

接下來我們觀察下在創(chuàng)建 OS 線程的時候是不是將 Thread 作為參數(shù)傳過去了,如果傳過去了,那就可以直接在新線程中拿到 Thread._startHelper._executionContext 字段,驗證起來也很簡單,在win32 的 ntdll!NtCreateThreadEx 上下一個斷點即可。

0:000> bp ntdll!NtCreateThreadEx
0:000> g
Breakpoint 1 hit
ntdll!NtCreateThreadEx:
00007ff9`0fe8e8c0 4c8bd1          mov     r10,rcx
0:000> r
rax=00007ff8b4a529d0 rbx=0000000000000000 rcx=0000008471b7df28
rdx=00000000001fffff rsi=0000027f2ca25b01 rdi=0000027f2ca25b60
rip=00007ff90fe8e8c0 rsp=0000008471b7de68 rbp=00007ff8b4a529d0
 r8=0000000000000000  r9=ffffffffffffffff r10=0000027f2c8a0000
r11=0000008471b7de40 r12=0000008471b7e890 r13=0000008471b7e4f8
r14=ffffffffffffffff r15=0000000000010000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
ntdll!NtCreateThreadEx:
00007ff9`0fe8e8c0 4c8bd1          mov     r10,rcx
0:000> !t
ThreadCount:      4
UnstartedThread:  1
BackgroundThread: 2
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1     2cd8 0000027F2C9E6610    2a020 Preemptive  0000027F2E5DB438:0000027F2E5DB4A0 0000027f2c9dd670 -00001 MTA 
   6    2     2b24 0000027F2CA121E0    21220 Preemptive  0000000000000000:0000000000000000 0000027f2c9dd670 -00001 Ukn (Finalizer) 
   7    3     2658 0000027F4EAA0AE0    2b220 Preemptive  0000000000000000:0000000000000000 0000027f2c9dd670 -00001 MTA 
XXXX    4        0 0000027F2CA25B60     9400 Preemptive  0000000000000000:0000000000000000 0000027f2c9dd670 -00001 Ukn 

從輸出中可以看到 NtCreateThreadEx 方法的第二個參數(shù)即 rdi=0000027f2ca25b60 就是我們的托管線程,如果你不相信的話可以再用 windbg 找到它的托管線程信息,輸出如下:

0:000> dt coreclr!Thread 0000027F2CA25B60 -y m_ExposedObject
   +0x1c8 m_ExposedObject : 0x0000027f`2c8f11d0 OBJECTHANDLE__

0:000> !do poi(0x0000027f`2c8f11d0)
Name:        System.Threading.Thread
MethodTable: 00007ff855090d78
EEClass:     00007ff85506a700
Tracked Type: false
Size:        72(0x48) bytes
File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.25\System.Private.CoreLib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff8550c76d8  4000b35        8 ....ExecutionContext  0 instance 0000000000000000 _executionContext
0000000000000000  4000b36       10 ...ronizationContext  0 instance 0000000000000000 _synchronizationContext
00007ff85508d708  4000b37       18        System.String  0 instance 0000000000000000 _name
00007ff8550cb9d0  4000b38       20 ...hread+StartHelper  0 instance 0000027f2e5db3b0 _startHelper
...

有些朋友可能要說,你現(xiàn)在的 _executionContext 字段是保留在 _startHelper 類里,并沒有賦值到Thread._executionContext字段呀?那這一塊在哪里實現(xiàn)的呢?從上圖可以看到其實是在新線程的執(zhí)行函數(shù)上,在托管函數(shù)執(zhí)行之前會將 _startHelper._executionContext 賦值給 Thread._executionContext , 讓 windbg 繼續(xù)執(zhí)行,輸出如下:

0:009> k
 # Child-SP          RetAddr               Call Site
00 00000084`728ff778 00007ff8`b4c23d19     KERNELBASE!wil::details::DebugBreak+0x2
01 00000084`728ff780 00007ff8`b43ba7ea     coreclr!DebugDebugger::Break+0x149 [D:\a\_work\1\s\src\coreclr\vm\debugdebugger.cpp @ 148] 
02 00000084`728ff900 00007ff8`54ff56e3     System_Private_CoreLib!System.Diagnostics.Debugger.Break+0xa [/_/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 18] 
03 00000084`728ff930 00007ff8`b42b4259     ConsoleApp9!ConsoleApp9.Program.<>c.<Main>b__1_0+0x113
04 00000084`728ff9c0 00007ff8`b42bddd9     System_Private_CoreLib!System.Threading.Thread.StartHelper.Callback+0x39 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 42] 
05 00000084`728ffa00 00007ff8`b42b2f4a     System_Private_CoreLib!System.Threading.ExecutionContext.RunInternal+0x69 [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 183] 
06 00000084`728ffa70 00007ff8`b4b7ba53     System_Private_CoreLib!System.Threading.Thread.StartCallback+0x8a [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 105] 
07 00000084`728ffab0 00007ff8`b4a763dc     coreclr!CallDescrWorkerInternal+0x83
08 00000084`728ffaf0 00007ff8`b4b5e713     coreclr!DispatchCallSimple+0x80 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 220] 
09 00000084`728ffb80 00007ff8`b4a52d25     coreclr!ThreadNative::KickOffThread_Worker+0x63 [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 158] 
...
0d (Inline Function) --------`--------     coreclr!ManagedThreadBase_FullTransition+0x2d [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7569] 
0e (Inline Function) --------`--------     coreclr!ManagedThreadBase::KickOff+0x2d [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7604] 
0f 00000084`728ffd60 00007ff9`0e777614     coreclr!ThreadNative::KickOffThread+0x79 [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 230] 
10 00000084`728ffdc0 00007ff9`0fe426a1     KERNEL32!BaseThreadInitThunk+0x14
11 00000084`728ffdf0 00000000`00000000     ntdll!RtlUserThreadStart+0x21
...

在上面的回調函數(shù)中看的非常清楚,在執(zhí)行托管函數(shù) <Main>b__1_0 之前執(zhí)行了一個 ExecutionContext.RunInternal 函數(shù),對,就是它來實現(xiàn)的,參考代碼如下:

    private sealed class StartHelper
    {
        internal void Run()
        {
            System.Threading.ExecutionContext.RunInternal(_executionContext, s_threadStartContextCallback, this);
        }
    }

    internal static void RunInternal(ExecutionContext executionContext, ContextCallback callback, object state)
    {
        Thread currentThread = Thread.CurrentThread;
        RestoreChangedContextToThread(currentThread, executionContext, executionContext3);
    }

    internal static void RestoreChangedContextToThread(Thread currentThread, ExecutionContext contextToRestore, ExecutionContext currentContext)
    {
        currentThread._executionContext = contextToRestore;
    }

既然將 StartHelper.executionContext 塞到了 currentThread._executionContext 中,在 <Main>b__1_0 方法中自然就能通過 _asyncLocal.Value 提取了。

三:總結

說了這么多,其實精妙之處在于創(chuàng)建OS線程的時候,會把C# Thread實例(coreclr對應線程) 作為參數(shù)傳遞給新線程,即下面方法簽名中的 lpParameter 參數(shù),新線程拿到了Thread實例,自然就能獲取到調用線程賦值的 Thread._executionContext 字段,所以這是完完全全的C#層面玩法,希望能給后來者解惑吧!

HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  [in]            SIZE_T                  dwStackSize,
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,
  [in]            DWORD                   dwCreationFlags,
  [out, optional] LPDWORD                 lpThreadId
);

到此這篇關于淺析C# AsyncLocal如何實現(xiàn)Thread間傳值的文章就介紹到這了,更多相關C# AsyncLocal實現(xiàn)Thread間傳值內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • c# 線性回歸和多項式擬合示例詳解

    c# 線性回歸和多項式擬合示例詳解

    線性回歸與多項式擬合是兩種常用的回歸分析方法,線性回歸模型簡單,易于計算,但只適用于線性關系的數(shù)據(jù),多項式擬合能處理非線性數(shù)據(jù),模型更復雜,擬合度更高,但容易產生過擬合問題,計算成本較高,適用場景不同,線性回歸適合線性數(shù)據(jù),多項式擬合適合非線性數(shù)據(jù)
    2024-10-10
  • C#版免費離線人臉識別之虹軟ArcSoft?V3.0(推薦)

    C#版免費離線人臉識別之虹軟ArcSoft?V3.0(推薦)

    本文只是簡單介紹了如何使用虹軟的離線SDK,進行人臉識別的方法,并且是圖片的方式,本地離線識別最大的好處就是沒有延遲,識別結果立馬呈現(xiàn),對C#離線人臉識別虹軟相關知識感興趣的朋友一起看看吧
    2021-12-12
  • 淺析C# 索引器(Indexer)

    淺析C# 索引器(Indexer)

    這篇文章主要介紹了C# 索引器(Indexer)的相關資料,文中示例代碼非常詳細,幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • 理解C#中的Lambda表達式

    理解C#中的Lambda表達式

    這篇文章主要介紹了理解C#中的Lambda表達式,本文用實例代碼來講解Lambda表達式,用不同的角度總結對它的認識,需要的朋友可以參考下
    2015-04-04
  • c#調用c++方法介紹,window api

    c#調用c++方法介紹,window api

    c#在調用c++方法或者window api時不能象調用c#本身寫的dll類庫那樣直接通過引用dll就可以調用相應的方法, 而是要把要引用的dll放到bin中,現(xiàn)通過[DllImport("um_web_client.dll")]引用
    2013-10-10
  • C#數(shù)據(jù)導入到EXCEL的方法

    C#數(shù)據(jù)導入到EXCEL的方法

    今天小編就為大家分享一篇關于C#數(shù)據(jù)導入到EXCEL的方法,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • C# 模擬瀏覽器并自動操作的實例代碼

    C# 模擬瀏覽器并自動操作的實例代碼

    這篇文章主要介紹了C# 模擬瀏覽器并自動操作的實例代碼,文中講解非常細致,幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • 用c#實現(xiàn)簡易的計算器功能實例代碼

    用c#實現(xiàn)簡易的計算器功能實例代碼

    這篇文章主要介紹了c#實現(xiàn)簡易的計算器功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-05-05
  • C#實現(xiàn)塊狀鏈表的項目實踐

    C#實現(xiàn)塊狀鏈表的項目實踐

    這篇文章主要介紹了C#實現(xiàn)塊狀鏈表的項目實踐,通過定義塊和鏈表類,利用塊內元素引用實現(xiàn)塊與塊之間的鏈接關系,從而實現(xiàn)對塊狀鏈表的遍歷、插入和刪除等操作,感興趣的可以了解一下
    2023-11-11
  • C#單線程和多線程的端口掃描器應用比較詳解

    C#單線程和多線程的端口掃描器應用比較詳解

    這篇文章主要詳細對比分析了C#單線程和多線程的端口掃描器應用,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07

最新評論