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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

浅谈AsyncLocal,我们应该知道的那些事儿

發(fā)布時間:2023/12/4 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浅谈AsyncLocal,我们应该知道的那些事儿 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
【導(dǎo)讀】最近查看有關(guān)框架源碼,發(fā)現(xiàn)AsyncLocal這玩意水還挺深,于是花了一點功夫去研究,同時對比ThreadLocal說明二者區(qū)別以及在何時場景下使用AsyncLocal或ThreadLocal

ThreadLocal相信很多童鞋用過,但AsyncLocal具體使用包括我在內(nèi)的一大部分童鞋應(yīng)該完全沒怎么使用過。

AsyncLocal同樣出現(xiàn)在.NET Framework 4.6+(包括4.6),當(dāng)然在.NET Core中沒有版本限制即CoreCLR,對此類官方所給的解釋是:將本地環(huán)境數(shù)據(jù)傳遞到異步控制流,例如異步方法

又例如緩存WCF通信通道,可以使用AsyncLocal而不是.NET Framework或CoreCLR所提供的ThreadLocal

官方概念解釋在我們初次聽來好像還是有點抽象,不打緊,接下來我們通過實際例子來進(jìn)行詳細(xì)說明和解釋

AsyncLocal和ThreadLocal區(qū)別

首先我們先看如下例子,然后再分析二者和什么有關(guān)系

private?static?readonly?ThreadLocal<string>?threadLocal?=?new?ThreadLocal<string>();private?static?readonly?AsyncLocal<string>?asyncLocal?=?new?AsyncLocal<string>();static?async?Task?Main(string[]?args) {threadLocal.Value?=?"threadLocal";asyncLocal.Value?=?"asyncLocal";await?Task.Yield();Console.WriteLine("After?await:?"?+?threadLocal.Value);Console.WriteLine("After?await:?"?+?asyncLocal.Value);Task.Run(()?=>?Console.WriteLine("Inside?child?task:?"?+?threadLocal.Value)).Wait();Task.Run(()?=>?Console.WriteLine("Inside?child?task:?"?+?asyncLocal.Value)).Wait();Console.ReadLine(); }

猜猜如上將會打印出什么結(jié)果呢?

為何ThreadLocal所打印的值為空值呢?我們不是設(shè)置了值嗎?此時我們將要從執(zhí)行環(huán)境開始說起

若完全理解ExecutionContext與SynchronizationContext二者概念和關(guān)系,理論上來講則可解答出上述問題,這里我們簡單敘述下,更詳細(xì)介紹請查閱相關(guān)資料自行了解

ExecutionContext俗稱“執(zhí)行上下文”,也就是說和“環(huán)境”信息相關(guān),這也就意味著它存儲著和我們當(dāng)前程序所執(zhí)行的環(huán)境相關(guān)的數(shù)據(jù),這類環(huán)境信息數(shù)據(jù)存儲在ThreadStatic或ThreadLocal中,換句話說ThreadLocal和特定線程相關(guān)

上述我們討論的是相同環(huán)境或上下文中,若是不同上下文即不同線程中,那情況又該如何呢?

在異步操作中,在某一個線程中啟動操作,但卻在另一線程中完成,此時我們將不能利用ThreadLocal來存儲數(shù)據(jù),因線程切換所需存儲數(shù)據(jù),我們可以稱之為環(huán)境“流動”

對于邏輯控制流,我們期望的是執(zhí)行環(huán)境相關(guān)數(shù)據(jù)能同控制流一起流動,以便能讓執(zhí)行環(huán)境相關(guān)數(shù)據(jù)能從一個線程移動到另外一個線程,ExecutionContext的作用就在于此。而SynchronizationContext是一種抽象,比如Windows窗體則提供了WindowsFormSynchronizationContext上下文等等

SynchronizationContext作為ExecutionContext執(zhí)行環(huán)境的一部分

ExecutionContext是當(dāng)前執(zhí)行環(huán)境,而SynchronizationContext則是針對不同框架或UI的抽象

我們可通過SynchronizationContext.Current得到當(dāng)前執(zhí)行環(huán)境信息。

到這里想必我們已經(jīng)明白基于特定線程的ThreadLocal在當(dāng)前線程設(shè)置值后,但await卻不在當(dāng)前線程,所以打印值為空,若將上述第一個await去除,則可打印出設(shè)置值,而AsyncLocal卻是和執(zhí)行環(huán)境相關(guān),也就是說與線程和調(diào)用堆棧有關(guān),并不針對特定線程,它是流動的。

AsyncLocal原理初步分析

首先我們通過一個簡單的例子來演示AsyncLocal類中值變化過程,我們能從表面上可得出的結(jié)論,然后最終結(jié)合源碼進(jìn)行進(jìn)一步分析

private?static?readonly?AsyncLocal<string>?asyncLocal?=?new?AsyncLocal<string>();static?async?Task?Main(string[]?args) {asyncLocal.Value?=?"asyncLocal";Task.Run(()?=>{asyncLocal.Value?=?"inside?child?task?asyncLocal";Console.WriteLine($"Inside?child?task:?{asyncLocal.Value}");}).Wait();Console.WriteLine($"after?await:{asyncLocal.Value}");Console.ReadLine(); }

由上打印我們可看出,在Task方法內(nèi)部將其值進(jìn)行了修改并打印出修改過后的結(jié)果,在Task結(jié)束后,最終打印的卻是初始值。

在Task方法內(nèi)部修改其值,但在任務(wù)結(jié)束后仍為初始值,這是一種“寫時復(fù)制”行為,AsyncLocal內(nèi)部做了兩步操作

進(jìn)行AsyncLocal實例的拷貝副本,但這是淺復(fù)制行為而非深復(fù)制

在設(shè)置新的值之前完成復(fù)制操作

接下來我們再通過一個層層調(diào)用例子并深入分析

private?static?readonly?AsyncLocal<string>?asyncLocal?=?new?AsyncLocal<string>();static?async?Task?Main(string[]?args) {Demo1().GetAwaiter().GetResult();Console.ReadLine(); }static?async?Task?Demo1() {await?Demo2();Console.WriteLine($"inside the method of demo1:{asyncLocal.Value}"); }static?async?Task?Demo2() {SetValue();Console.WriteLine($"inside the method of demo2:{asyncLocal.Value}"); }static?void?SetValue() {asyncLocal.Value?=?"initial?value"; }

我們看到此時在Demo1方法內(nèi)部打印值為空,因為在Demo2方法內(nèi)部并未使用異步,所以能打印出所設(shè)置的值,這說明如下問題

每次進(jìn)行實際的aysnc/await后,都會啟動一個新的異步上下文,并且該上下文與父異步上下文完全隔離且獨立,換句話說,在異步方法內(nèi),可查詢自己所屬AsyncLocal<T>,以便能確保不會污染父異步上下文,因為所做更改完全是針對當(dāng)前異步上下文的本地內(nèi)容

至于為何在Demo1方法內(nèi)部打印為空,想必我們已經(jīng)很清晰,當(dāng)async方法返回時,返回的是父異步上下文,此時將看不到任何子異步上下文所執(zhí)行的修改。

AsyncLocal原理源碼分析

我們來到AsyncLocal類,通過屬性Value設(shè)置值,內(nèi)部通過調(diào)用ExecutionContext類中的SetLocalValue方法進(jìn)行設(shè)置,源碼如下:

internal?static?void?SetLocalValue(IAsyncLocal?local,?object??newValue,?bool?needChangeNotifications) {ExecutionContext??current?=?Thread.CurrentThread._executionContext;object??previousValue?=?null;bool?hadPreviousValue?=?false;if?(current?!=?null){hadPreviousValue?=?current.m_localValues.TryGetValue(local,?out?previousValue);}if?(previousValue?==?newValue){return;}IAsyncLocal[]??newChangeNotifications?=?null;IAsyncLocalValueMap?newValues;bool?isFlowSuppressed?=?false;if?(current?!=?null){isFlowSuppressed?=?current.m_isFlowSuppressed;newValues?=?current.m_localValues.Set(local,?newValue,?treatNullValueAsNonexistent:?!needChangeNotifications);newChangeNotifications?=?current.m_localChangeNotifications;}else{newValues?=?AsyncLocalValueMap.Create(local,?newValue,?treatNullValueAsNonexistent:?!needChangeNotifications);}if?(needChangeNotifications){if?(hadPreviousValue){Debug.Assert(newChangeNotifications?!=?null);Debug.Assert(Array.IndexOf(newChangeNotifications,?local)?>=?0);}else?if?(newChangeNotifications?==?null){newChangeNotifications?=?new?IAsyncLocal[1]?{?local?};}else{int?newNotificationIndex?=?newChangeNotifications.Length;Array.Resize(ref?newChangeNotifications,?newNotificationIndex?+?1);newChangeNotifications[newNotificationIndex]?=?local;}}Thread.CurrentThread._executionContext?=(!isFlowSuppressed?&&?AsyncLocalValueMap.IsEmpty(newValues))??null?:?new?ExecutionContext(newValues,?newChangeNotifications,?isFlowSuppressed);if?(needChangeNotifications){local.OnValueChanged(previousValue,?newValue,?contextChanged:?false);} }

當(dāng)首次設(shè)置值時,我們通過Thread.CurrentThread.ExecutionContext,獲取其屬性將為空,通過AsyncLocalValueMap.Create創(chuàng)建一個AsyncLocal實例并設(shè)置值

同時我們也可以看到,若在同一執(zhí)行環(huán)境中,當(dāng)前最新設(shè)置值與之前所設(shè)置值相同,此時將不會是覆蓋,而是直接返回。

我們直接來到最后如下幾行代碼:

Thread.CurrentThread._executionContext?=(!isFlowSuppressed?&&?AsyncLocalValueMap.IsEmpty(newValues))??null?:?new?ExecutionContext(newValues,?newChangeNotifications,?isFlowSuppressed);

若默認(rèn)使用Task默認(rèn)線程池調(diào)度,即使線程池重用線程,其執(zhí)行環(huán)境上下文也會不同,如此可說明將更能保證不會將線程數(shù)據(jù)泄露到另外一個線程中,也就是說在重用線程時,但將會保證異步本地實例會按照預(yù)期進(jìn)行GC(個人以為,理論上情況應(yīng)該是這樣,這樣也能保證AsyncLocal是安全的)。

至于其他關(guān)于如何進(jìn)行值更改后事件通知,這里就不再額外展開敘述

由于AsyncLocal使用淺拷貝,我們應(yīng)保證存儲的數(shù)據(jù)類型不可變,若要修改AsyncLocal<T>實例值,必須保證異步上下文隔離且相互不會影響。

到這里我們已完全清楚,AsyncLocal是針對異步控制流的良好支持,且數(shù)據(jù)可流動,當(dāng)前線程AsyncLocal實例所存儲的數(shù)據(jù)可流動到異步任務(wù)控制流中的默認(rèn)任務(wù)調(diào)度線程池的線程中

當(dāng)然我們也可以調(diào)用如下執(zhí)行環(huán)境上下文中的抑制流動方法來禁用數(shù)據(jù)流動

private?static?readonly?AsyncLocal<string>?asyncLocal?=?new?AsyncLocal<string>();static?async?Task?Main(string[]?args) {asyncLocal.Value?=?"asyncLocal";using?(ExecutionContext.SuppressFlow()){Task.Run(()?=>{Console.WriteLine($"Inside?child?task:?{asyncLocal.Value}");}).Wait();}Console.WriteLine($"after?await:{asyncLocal.Value}");Console.ReadLine(); }

此時在其任務(wù)內(nèi)部打印的值將為空。最后,我們再來對AsyncLocal做一個最終總結(jié)

?????AsyncLocal出現(xiàn)于.NET Framework 4.6+(包含4.6)、CoreCLR

?????AsyncLocal是每個ExecutionContext實例的一個變量,它并非如同ThreadLocal基于特定線程的持久化數(shù)據(jù)存儲

?????若需要基于本地環(huán)境的異步控制流,使用AsyncLocal而非ThreadLocal,在線程池中重用線程時,ThreadLocal會保留之前值(基于理論猜測),而AsyncLocal不會

?????AsyncLocal在每次asyn/await后,都將重新生成一個新的異步執(zhí)行上下文環(huán)境,父異步上下文執(zhí)行環(huán)境和子異步上下文執(zhí)行環(huán)境完全隔離且互不影響

?????AsyncLocal進(jìn)行異步控制流時,由于內(nèi)部對數(shù)據(jù)進(jìn)行淺拷貝,確保其實例類型參數(shù)應(yīng)為不可變數(shù)據(jù)類型

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

總結(jié)

以上是生活随笔為你收集整理的浅谈AsyncLocal,我们应该知道的那些事儿的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。