在非同步程式設計中,處理非同步操作之間的資料流轉是一個比較常用的操作。C#
非同步程式設計提供了一個強大的工具來解決這個問題,那就是AsyncLocal
。它是一個執行緒本地儲存的機制,可以在非同步操作之間傳遞資料。它為我們提供了一種簡單而可靠的方式來共用資料,而不必擔心執行緒切換或非同步上下文的變化。本文我們將探究AsyncLocal
的原理和用法,並進行相關原始碼解析。探討它如何在非同步操作之間實現資料的流轉,以及它是如何在底層工作的。
上面我們提到了AsyncLocal
可以在非同步操作間傳遞資料,我們在之前的文章<研究c#非同步操作async await狀態機的總結>一文中提到過非同步操作會涉及到執行緒切換的問題,接下來通過Task來模擬一個簡單非同步範例,來看一下它的工作方式是什麼樣的,以便加深對它的理解,先看一下範例
AsyncLocal<Person> context = new AsyncLocal<Person>();
context.Value = new Person { Id = 1, Name = "張三" };
Console.WriteLine($"Main之前:{context.Value.Name},ThreadId={Thread.CurrentThread.ManagedThreadId}");
await Task.Run(() =>
{
Console.WriteLine($"Task1之前:{context.Value.Name},ThreadId={Thread.CurrentThread.ManagedThreadId}");
context.Value.Name = "李四";
Console.WriteLine($"Task1之後:{context.Value.Name},ThreadId={Thread.CurrentThread.ManagedThreadId}");
});
await Task.Run(() =>
{
Console.WriteLine($"Task2之前:{context.Value.Name},ThreadId={Thread.CurrentThread.ManagedThreadId}");
context.Value.Name = "王五";
Console.WriteLine($"Task2之後:{context.Value.Name},ThreadId={Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine($"Main之後:{context.Value.Name},ThreadId={Thread.CurrentThread.ManagedThreadId}");
在上面的範例中,我們建立了一個AsyncLocal
範例,並賦值了一個Person
物件,然後我們建立了兩個Task
,分別執行了兩個非同步操作,並分別修改了AsyncLocal
中的Person
物件的值,分別在執行非同步之前執行非同步過程中和執行非同步之後列印值來觀察變化,執行程式輸出結果如下
Main之前:張三,ThreadId=1
Task1之前:張三,ThreadId=4
Task1之後:李四,ThreadId=4
Task2之前:李四,ThreadId=6
Task2之後:王五,ThreadId=6
Main之後:王五,ThreadId=6
從輸出結果來看,雖然我們在非同步中修改了AsyncLocal
裡Person
物件的值,並且也發生了執行緒切換。但是它可以在非同步操作之間的資料共用和傳遞,使得我們在非同步間進行的資料就和在一個執行緒裡運算元據一樣,讓我們可以忽略掉其實已經發生了多次執行緒切換。
通過上面的範例,我們發現AsyncLocal
確實可以實現非同步之間的資料共用和傳遞,那麼它是如何實現的呢?接下來,我們通過先檢視AsyncLocal
涉及到的相關原始碼來探究一下。想弄明白它的流轉問題,需要研究兩個大方向,一個是AsyncLocal
的本身實現,一個是AsyncLocal
的流轉涉及到的非同步或者多執行緒相關這裡涉及到的主要是Task
和執行緒池
裡的相關實現。由於非同步相關涉及到了一整個體系,所以但看某一點的時候可能不太容易理解,我們先從AsyncLocal
本身入手,然後從Task
入手,最後從執行緒池
入手,逐步探究AsyncLocal
如何進行資料流轉的。但是仍然希望能在閱讀本文之前先了解一下設計到該話題的相關文章,先對整體有一個整體的把握
雖然強烈建議先看一下上面推薦的文章,但是在這裡我們還是簡單介紹一下AsyncLocal
的實現,所以這裡我們簡單介紹一下,方便大家能直觀的看到。其實涉及到的比較簡單,就是看一下AsyncLocal
裡涉及到關於Value的操作即可[點選檢視AsyncLocal.Value原始碼