最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺析C#異步中的Overlapped是如何尋址的

 更新時間:2025年01月07日 10:41:53   作者:一線碼農(nóng)  
用ReadAsync做文件異步讀取時,在Win32層面會傳lpOverlapped到內(nèi)核層,那在內(nèi)核層回頭時,它是如何通過這個lpOverlapped尋找到?ReadAsync這個異步的Task的呢,下面我們就來簡單分析一下吧

一:背景

1. 講故事

前段時間訓(xùn)練營里的一位朋友提了一個問題,我用ReadAsync做文件異步讀取時,我知道在Win32層面會傳 lpOverlapped 到內(nèi)核層,那在內(nèi)核層回頭時,它是如何通過這個 lpOverlapped 尋找到 ReadAsync 這個異步的Task的呢?

這是一個好問題,這需要回答人對異步完整的運(yùn)轉(zhuǎn)流程有一個清晰的認(rèn)識,即使有清晰的認(rèn)識也不能很好的口頭表述出來,就算表述出來對方也不一定能聽懂,所以干脆開兩篇文章來嘗試解讀一下吧。

二:lpOverlapped 如何映射

1. 測試案例

為了能夠講清楚,我們先用 fileStream.ReadAsync 方法來寫一段異步讀取來產(chǎn)生Overlapped,參考代碼如下:

        static void Main(string[] args)
        {
            UseAwaitAsync();
            Console.ReadLine();
        }

        static async Task<string> UseAwaitAsync()
        {
            string filePath = "D:\\dumps\\trace-1\\GenHome.DMP";
            Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} 請求發(fā)起...");
            FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 16, useAsync: true);
            {
                byte[] buffer = new byte[fileStream.Length];

                int bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length);

                string content = Encoding.UTF8.GetString(buffer, 0, bytesRead);

                var query = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} 獲取到結(jié)果:{content.Length}";

                Console.WriteLine(query);

                return query;
            }
        }

很顯然上面的方法會調(diào)用 Win32 中的 ReadFile,接下來上一下它的簽名和 _OVERLAPPED 結(jié)構(gòu)體。

BOOL ReadFile(
  [in]                HANDLE       hFile,
  [out]               LPVOID       lpBuffer,
  [in]                DWORD        nNumberOfBytesToRead,
  [out, optional]     LPDWORD      lpNumberOfBytesRead,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    } DUMMYSTRUCTNAME;
    PVOID Pointer;
  } DUMMYUNIONNAME;
  HANDLE    hEvent;
} OVERLAPPED, *LPOVERLAPPED;

2. 尋找映射的兩端

既然是映射嘛,肯定要找到兩個端口,即非托管層的 NativeOverlapped 和 托管層的 ThreadPoolBoundHandleOverlapped。

1.非托管 _OVERLAPPED

在 C# 中用 NativeOverlapped 結(jié)構(gòu)體表示 Win32 的 _OVERLAPPED 結(jié)構(gòu),參考如下:

public struct NativeOverlapped
{
	public nint InternalLow;
	public nint InternalHigh;
	public int OffsetLow;
	public int OffsetHigh;
	public nint EventHandle;
}

2.托管 ThreadPoolBoundHandleOverlapped

ReadAsync 所產(chǎn)生的 Task<int> 在底層是經(jīng)過ValueTask, OverlappedValueTaskSource 一陣痙攣后弄出來的,最后會藏匿在 Overlapped 子類的 ThreadPoolBoundHandleOverlapped 中,參考代碼和模型圖如下:

        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            ValueTask<int> valueTask = this.ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken);
            if (!valueTask.IsCompletedSuccessfully)
            {
                return valueTask.AsTask();
            }
            return this._lastSyncCompletedReadTask.GetTask(valueTask.Result);
        }

        private unsafe static ValueTuple<SafeFileHandle.OverlappedValueTaskSource, int> QueueAsyncReadFile(SafeFileHandle handle, Memory<byte> buffer, long fileOffset, CancellationToken cancellationToken, OSFileStreamStrategy strategy)
        {
            SafeFileHandle.OverlappedValueTaskSource overlappedValueTaskSource = handle.GetOverlappedValueTaskSource();
            
            NativeOverlapped* ptr = overlappedValueTaskSource.PrepareForOperation(buffer, fileOffset, strategy);
            if (Interop.Kernel32.ReadFile(handle, (byte*)overlappedValueTaskSource._memoryHandle.Pointer, buffer.Length, IntPtr.Zero, ptr) == 0)
            {
                overlappedValueTaskSource.RegisterForCancellation(cancellationToken);
            }
            overlappedValueTaskSource.FinishedScheduling();
            return new ValueTuple<SafeFileHandle.OverlappedValueTaskSource, int>(overlappedValueTaskSource, -1);
        }

最后就是兩端的映射關(guān)系了,先通過 malloc 分配了一塊私有內(nèi)存,中間隔了一個refcount 的 8byte大小,模型圖如下:

3. 眼見為實(shí)

要想眼見為實(shí),可以從C#源碼中的Overlapped.AllocateNativeOverlapped方法尋找答案。

    public unsafe class Overlapped
    {
        private NativeOverlapped* AllocateNativeOverlapped(object? userData)
        {
            NativeOverlapped* pNativeOverlapped = null;

            nuint handleCount = 1;

            pNativeOverlapped = (NativeOverlapped*)NativeMemory.Alloc((nuint)(sizeof(NativeOverlapped) + sizeof(nuint)) + handleCount * (nuint)sizeof(GCHandle));

            GCHandleCountRef(pNativeOverlapped) = 0;

            pNativeOverlapped->InternalLow = default;
            pNativeOverlapped->InternalHigh = default;
            pNativeOverlapped->OffsetLow = _offsetLow;
            pNativeOverlapped->OffsetHigh = _offsetHigh;
            pNativeOverlapped->EventHandle = _eventHandle;

            GCHandleRef(pNativeOverlapped, 0) = GCHandle.Alloc(this);
            GCHandleCountRef(pNativeOverlapped)++;

            return pRet;
        }

        private static ref nuint GCHandleCountRef(NativeOverlapped* pNativeOverlapped)
                               => ref *(nuint*)(pNativeOverlapped + 1);

        private static ref GCHandle GCHandleRef(NativeOverlapped* pNativeOverlapped, nuint index)
                              => ref *((GCHandle*)((nuint*)(pNativeOverlapped + 1) + 1) + index);
    }

卦中代碼先用 NativeMemory.Alloc 方法分配了一塊私有內(nèi)存,隨后還把 Overlapped 給 GCHandle.Alloc 住了,這是防止異步期間對象被移動,有了代碼接下來上windbg去眼見為實(shí),在 Kernel32!ReadFile 中下斷點(diǎn)觀察方法的第五個參數(shù)。

0:000> bp Kernel32!ReadFile
0:000> g
Breakpoint 0 hit
KERNEL32!ReadFile:
00007ffd`fa2f56a0 ff25caca0500    jmp     qword ptr [KERNEL32!_imp_ReadFile (00007ffd`fa352170)] ds:00007ffd`fa352170={KERNELBASE!ReadFile (00007ffd`f85c5520)}
0:000> k 5
 # Child-SP          RetAddr               Call Site
00 000000ff`8837e1c8 00007ffd`96229ce3     KERNEL32!ReadFile
01 000000ff`8837e1d0 00007ffd`96411a4a     System_Private_CoreLib!Interop.Kernel32.ReadFile+0xa3 [/_/src/coreclr/System.Private.CoreLib/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 6797] 
02 000000ff`8837e2d0 00007ffd`96411942     System_Private_CoreLib!System.IO.RandomAccess.QueueAsyncReadFile+0x8a
03 000000ff`8837e350 00007ffd`96433677     System_Private_CoreLib!System.IO.RandomAccess.ReadAtOffsetAsync+0x112 [/_/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs @ 238] 
04 000000ff`8837e3f0 00007ffd`9642d5f8     System_Private_CoreLib!System.IO.Strategies.OSFileStreamStrategy.ReadAsync+0xb7 [/_/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs @ 290] 
0:000> uf 00007ffd`96229ce3
...
 6797 00007ffd`96229c98 4c8b7d30        mov     r15,qword ptr [rbp+30h]
 6797 00007ffd`96229c9c 4c897c2420      mov     qword ptr [rsp+20h],r15
 6797 00007ffd`96229ca1 498bce          mov     rcx,r14
 6797 00007ffd`96229ca4 48894dac        mov     qword ptr [rbp-54h],rcx
 6797 00007ffd`96229ca8 488bd3          mov     rdx,rbx
 6797 00007ffd`96229cab 488955a4        mov     qword ptr [rbp-5Ch],rdx
 6797 00007ffd`96229caf 448bc6          mov     r8d,esi
 6797 00007ffd`96229cb2 448945b4        mov     dword ptr [rbp-4Ch],r8d
 6797 00007ffd`96229cb6 4c8bcf          mov     r9,rdi
 6797 00007ffd`96229cb9 4c894d9c        mov     qword ptr [rbp-64h],r9
 6797 00007ffd`96229cbd 488d8d40ffffff  lea     rcx,[rbp-0C0h]
 6797 00007ffd`96229cc4 ff159e909e00    call    qword ptr [System_Private_CoreLib!Interop.CallStringMethod+0x5ab9c8 (00007ffd`96c12d68)]
 6797 00007ffd`96229cca 488b055708a100  mov     rax,qword ptr [System_Private_CoreLib!Interop.CallStringMethod+0x5d3188 (00007ffd`96c3a528)]
 6797 00007ffd`96229cd1 488b4dac        mov     rcx,qword ptr [rbp-54h]
 6797 00007ffd`96229cd5 488b55a4        mov     rdx,qword ptr [rbp-5Ch]
 6797 00007ffd`96229cd9 448b45b4        mov     r8d,dword ptr [rbp-4Ch]
 6797 00007ffd`96229cdd 4c8b4d9c        mov     r9,qword ptr [rbp-64h]
 6797 00007ffd`96229ce1 ff10            call    qword ptr [rax]
 6797 00007ffd`96229ce3 8bd8            mov     ebx,eax

仔細(xì)閱讀卦中的匯編代碼,通過這句 r15,qword ptr [rbp+30h] 可知 pNativeOverlapped 是保存在 r15 寄存器中。

0:000> r r15
r15=00000241ca2d4d70
0:000> dp 00000241ca2d4d70
00000241`ca2d4d70  00000000`00000000 00000000`00000000
00000241`ca2d4d80  00000000`00000000 00000000`00000000
00000241`ca2d4d90  00000000`00000001 00000241`c8761358

根據(jù)上面的模型圖,00000241ca2d4d90 保存的是引用計(jì)數(shù),00000241c8761358 就是我們的 ThreadPoolBoundHandleOverlapped ,可以 !do 它一下便知。

最后用 dnspy 在 Overlapped.GetOverlappedFromNative 方法中下一個斷點(diǎn),這個方法會在異步處理完成后,執(zhí)行NativeOverlapped尋址ThreadPoolBoundHandleOverlapped 的邏輯,截圖如下,那個 ReadAsync保存在內(nèi)部的 _continuationState 字段里。

三:總結(jié)

C#的傳統(tǒng)做法大多都是采用傳參數(shù)的方式來建議映射關(guān)系,而本篇中用 malloc 開辟一塊私有區(qū)域來映射兩者的關(guān)系也真是獨(dú)一份,實(shí)屬無奈!

到此這篇關(guān)于淺析C#異步中的Overlapped是如何尋址的的文章就介紹到這了,更多相關(guān)C#異步Overlapped如何尋址內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • c#異常處理示例分享

    c#異常處理示例分享

    這篇文章主要介紹了c#異常處理示例,需要的朋友可以參考下
    2014-04-04
  • C#計(jì)算程序執(zhí)行過程花費(fèi)時間的方法

    C#計(jì)算程序執(zhí)行過程花費(fèi)時間的方法

    這篇文章主要介紹了C#計(jì)算程序執(zhí)行過程花費(fèi)時間的方法,涉及C#簡單的時間運(yùn)算技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-09-09
  • c#方法重寫和隱藏的學(xué)習(xí)示例

    c#方法重寫和隱藏的學(xué)習(xí)示例

    最近正在學(xué)習(xí)c#,對其中的方法重寫和隱藏的概念很是模糊,現(xiàn)在將其歸納如下供大家參考
    2014-01-01
  • C#如何優(yōu)雅的結(jié)束線程

    C#如何優(yōu)雅的結(jié)束線程

    這篇文章主要介紹了C#如何優(yōu)雅的結(jié)束一個線程,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-05-05
  • C#適配器模式的使用

    C#適配器模式的使用

    本文主要介紹了C#適配器模式的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 使用C#代碼實(shí)現(xiàn)Word文檔查找與替換功能

    使用C#代碼實(shí)現(xiàn)Word文檔查找與替換功能

    在編輯文檔時,更改文本是一項(xiàng)常見的任務(wù),Microsoft Word 提供了一個功能強(qiáng)大的查找和替換功能,本文將介紹如何使用 Spire.Doc for .NET 在 C# 中實(shí)現(xiàn) Word 文檔中的查找與替換功能,需要的朋友可以參考下
    2025-11-11
  • C#中關(guān)于可空類型的小知識

    C#中關(guān)于可空類型的小知識

    這篇文章主要介紹了C#中關(guān)于可空類型的小知識,本文講解可空類型運(yùn)算的小技巧,需要的朋友可以參考下
    2015-04-04
  • 使用C#代碼在Excel中獲取工作表名稱的操作指南

    使用C#代碼在Excel中獲取工作表名稱的操作指南

    在 Excel 中,工作表名稱可以作為工作簿內(nèi)容的一種元數(shù)據(jù),通過獲取這些名稱的列表,可以大致了解每個工作表的用途,本文將介紹如何使用 Spire.XLS for .NET 在 C# 中獲取 Excel 工作表名稱,需要的朋友可以參考下
    2026-01-01
  • c#創(chuàng)建浮動工具欄功能示例

    c#創(chuàng)建浮動工具欄功能示例

    這篇文章主要介紹了c#創(chuàng)建浮動工具欄功能示例,大家參考使用吧
    2013-12-12
  • C#使用 Salt + Hash 來為密碼加密

    C#使用 Salt + Hash 來為密碼加密

    本文主要介紹了幾種常見的破解密碼的方法,為密碼加鹽(Salt)以及在.NET中的實(shí)現(xiàn)等。具有一定的參考價值,下面跟著小編一起來看下吧
    2017-01-01

最新評論

新乡市| 巢湖市| 石阡县| 阳城县| 揭东县| 塔河县| 四川省| 石河子市| 广饶县| 城口县| 文安县| 塘沽区| 普兰店市| 东辽县| 灯塔市| 文昌市| 太白县| 米易县| 景德镇市| 朔州市| 五华县| 莒南县| 南安市| 剑川县| 九台市| 潢川县| 新乐市| 太和县| 赞皇县| 启东市| 县级市| 图木舒克市| 夹江县| 盐边县| 延庆县| 平罗县| 乐安县| 东阿县| 宜君县| 富民县| 建平县|