国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

.NET的AsyncLocal用法指南

這篇具有很好參考價值的文章主要介紹了.NET的AsyncLocal用法指南。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

AsyncLocal用法簡介

通過 AsyncLocal 我們可以在一個邏輯上下文中維護一份私有數(shù)據(jù),該上下文后續(xù)代碼中都可以訪問和修改這份數(shù)據(jù),但另一個無關(guān)的上下文是無法訪問的。

無論是在新創(chuàng)建的 Task 中還是 await 關(guān)鍵詞之后,我們都能夠訪問前面設(shè)置的 AsyncLocal 的數(shù)據(jù)。

class Program
{
    private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>();
    
    static async Task Main(string[] args)
    {
        _asyncLocal.Value = "Hello World!";
        Task.Run(() => Console.WriteLine($"AsyncLocal in task: {_asyncLocal.Value}"));

        await FooAsync();
        Console.WriteLine($"AsyncLocal after await FooAsync: {_asyncLocal.Value}");
    }

    private static async Task FooAsync()
    {
        await Task.Delay(100);
        Console.WriteLine($"AsyncLocal after await in FooAsync: {_asyncLocal.Value}");
    }
}

輸出結(jié)果:

AsyncLocal in task: Hello World!
AsyncLocal after await in FooAsync: Hello World!
AsyncLocal after await FooAsync: Hello World!

AsyncLocal實現(xiàn)原理

AsyncLocal 的實際數(shù)據(jù)存儲在 ExecutionContext 中,而 ExecutionContext 作為線程的私有字段與線程綁定,在線程會發(fā)生切換的地方,runtime 會將切換前的 ExecutionContext 保存起來,切換后再恢復(fù)到新線程上。

這個保存和恢復(fù)的過程是由 runtime 自動完成的,例如會發(fā)生在以下幾個地方:

  • new Thread(ThreadStart start).Start()
  • Task.Run(Action action)
  • ThreadPool.QueueUserWorkItem(WaitCallback callBack)
  • await 之后

以 await 為例,當我們在一個方法中使用了 await 關(guān)鍵詞,編譯器會將這個方法編譯成一個狀態(tài)機,這個狀態(tài)機會在 await 之前和之后分別保存和恢復(fù) ExecutionContext。

class Program
{
    private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>();
    
    static async Task Main(string[] args)
    {
        _asyncLocal.Value = "Hello World!";
        await FooAsync();
        Console.WriteLine($"AsyncLocal after await FooAsync: {_asyncLocal.Value}");
    }

    private static async Task FooAsync()
    {
        await Task.Delay(100);
    }
}

輸出結(jié)果:

AsyncLocal after await FooAsync: Hello World!

.NET的AsyncLocal用法指南

AsyncLocal的坑

有時候我們會在 FooAsync 方法中去修改 AsyncLocal 的值,并希望在 Main 方法在 await FooAsync 之后能夠獲取到修改后的值,但是實際上這是不可能的。

class Program
{
    private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>();
    
    static async Task Main(string[] args)
    {
        _asyncLocal.Value = "A";
        Console.WriteLine($"AsyncLocal before FooAsync: {_asyncLocal.Value}");
        await FooAsync();
        Console.WriteLine($"AsyncLocal after await FooAsync: {_asyncLocal.Value}");
    }

    private static async Task FooAsync()
    {
        _asyncLocal.Value = "B";
        Console.WriteLine($"AsyncLocal before await in FooAsync: {_asyncLocal.Value}");
        await Task.Delay(100);
        Console.WriteLine($"AsyncLocal after await in FooAsync: {_asyncLocal.Value}");
    }
}

輸出結(jié)果:

AsyncLocal before FooAsync: A
AsyncLocal before await in FooAsync: B
AsyncLocal after await in FooAsync: B
AsyncLocal after await FooAsync: A

為什么我們在 FooAsync 方法中修改了 AsyncLocal 的值,但是在 await FooAsync 之后,AsyncLocal 的值卻沒有被修改呢?

原因是 ExecutionContext 被設(shè)計成了一個不可變的對象,當我們在 FooAsync 方法中修改了 AsyncLocal 的值,實際上是創(chuàng)建了一個新的 ExecutionContext,原來其他的 AsyncLocal 的值被值拷貝到了新的 ExecutionContext 中,新的 AsyncLocal 的值只會寫入到新的 ExecutionContext 中,而原來的 ExecutionContext 及其關(guān)聯(lián)的 AsyncLocal 仍然保持不變。

這樣的設(shè)計是為了保證線程的安全性,因為在多線程環(huán)境下,如果 ExecutionContext 是可變的,那么在切換線程的時候,可能會出現(xiàn)數(shù)據(jù)不一致的情況。

我們通常把這種設(shè)計稱為 Copy On Write(簡稱COW),即在修改數(shù)據(jù)的時候,會先拷貝一份數(shù)據(jù),然后在拷貝的數(shù)據(jù)上進行修改,這樣就不會影響到原來的數(shù)據(jù)。

ExecutionContext 中可能不止一個 AsyncLocal 的數(shù)據(jù),修改任意一個 AsyncLocal 都會導致 ExecutionContext 的 COW。

所以上面代碼的執(zhí)行過程如下:

.NET的AsyncLocal用法指南

AsyncLocal的避坑指南

那么我們?nèi)绾卧?FooAsync 方法中修改 AsyncLocal 的值,并且在 Main 方法中獲取到修改后的值呢?

我們需要借助一個中介者,讓中介者來保存 AsyncLocal 的值,然后在 FooAsync 方法中修改中介者的屬性值,這樣就可以在 Main 方法中獲取到修改后的值了。

下面我們設(shè)計一個 ValueHolder 來保存 AsyncLocal 的值,修改 Value 并不會修改 AsyncLocal 的值,而是修改 ValueHolder 的屬性值,這樣就不會觸發(fā) ExecutionContext 的 COW。

我們還需要設(shè)計一個 ValueAccessor 來封裝 ValueHolder 對值的訪問和修改,這樣可以保證 ValueHolder 的值只能在 ValueAccessor 中被修改。

class ValueAccessor<T> : IValueAccessor<T>
{
    private static AsyncLocal<ValueHolder<T>> _asyncLocal = new AsyncLocal<ValueHolder<T>>();

    public T Value
    {
        get => _asyncLocal.Value != null ? _asyncLocal.Value.Value : default;
        set
        {
            _asyncLocal.Value ??= new ValueHolder<T>();

            _asyncLocal.Value.Value = value;
        }
    }
}

class ValueHolder<T>
{
    public T Value { get; set; }
}

class Program
{
    private static IValueAccessor<string> _valueAccessor = new ValueAccessor<string>();

    static async Task Main(string[] args)
    {
        _valueAccessor.Value = "A";
        Console.WriteLine($"ValueAccessor before await FooAsync in Main: {_valueAccessor.Value}");
        await FooAsync();
        Console.WriteLine($"ValueAccessor after await FooAsync in Main: {_valueAccessor.Value}");
    }

    private static async Task FooAsync()
    {
        _valueAccessor.Value = "B";
        Console.WriteLine($"ValueAccessor before await in FooAsync: {_valueAccessor.Value}");
        await Task.Delay(100);
        Console.WriteLine($"ValueAccessor after await in FooAsync: {_valueAccessor.Value}");
    }
}

輸出結(jié)果:

ValueAccessor before await FooAsync in Main: A
ValueAccessor before await in FooAsync: B
ValueAccessor after await in FooAsync: B
ValueAccessor after await FooAsync in Main: B

HttpContextAccessor的實現(xiàn)原理

我們常用的?HttpContextAccessor?通過HttpContextHolder?來間接地在?AsyncLocal?中存儲?HttpContext。

如果要更新 HttpContext,只需要在 HttpContextHolder 中更新即可。因為 AsyncLocal 的值不會被修改,更新 HttpContext 時 ExecutionContext 也不會出現(xiàn) COW 的情況。

不過 HttpContextAccessor 中的邏輯有點特殊,它的 HttpContextHolder 是為保證清除 HttpContext 時,這個 HttpContext 能在所有引用它的 ExecutionContext 中被清除(可能因為修改 HttpContextHolder 之外的 AsyncLocal 數(shù)據(jù)導致 ExecutionContext 已經(jīng) COW 很多次了)。

下面是 HttpContextAccessor 的實現(xiàn),英文注釋是原文,中文注釋是我自己的理解。

/// </summary>
public class HttpContextAccessor : IHttpContextAccessor
{
    private static readonly AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();

    /// <inheritdoc/>
    public HttpContext? HttpContext
    {
        get
        {
            return _httpContextCurrent.Value?.Context;
        }
        set
        {
            var holder = _httpContextCurrent.Value;
            if (holder != null)
            {
                // Clear current HttpContext trapped in the AsyncLocals, as its done.
                // 這邊的邏輯是為了保證清除 HttpContext 時,這個 HttpContext 能在所有引用它的 ExecutionContext 中被清除
                holder.Context = null;
            }

            if (value != null)
            {
                // Use an object indirection to hold the HttpContext in the AsyncLocal,
                // so it can be cleared in all ExecutionContexts when its cleared.
                // 這邊直接修改了 AsyncLocal 的值,所以會導致 ExecutionContext 的 COW。新的 HttpContext 不會被傳遞到原先的 ExecutionContext 中。
                _httpContextCurrent.Value = new HttpContextHolder { Context = value };
            }
        }
    }

    private sealed class HttpContextHolder
    {
        public HttpContext? Context;
    }
}

但 HttpContextAccessor 的實現(xiàn)并不允許將新賦值的非 null 的 HttpContext 傳遞到外層的 ExecutionContext 中,可以參考上面的源碼及注釋理解。

class Program
{
    private static IHttpContextAccessor _httpContextAccessor = new HttpContextAccessor();
    
    static async Task Main(string[] args)
    {
        var httpContext = new DefaultHttpContext
        {
            Items = new Dictionary<object, object>
            {
                { "Name", "A"}
            }
        };
        _httpContextAccessor.HttpContext = httpContext;
        Console.WriteLine($"HttpContext before await FooAsync in Main: {_httpContextAccessor.HttpContext.Items["Name"]}");
        await FooAsync();
        // HttpContext 被清空了,下面這行輸出 null
        Console.WriteLine($"HttpContext after await FooAsync in Main: {_httpContextAccessor.HttpContext?.Items["Name"]}");
    }

    private static async Task FooAsync()
    {
        _httpContextAccessor.HttpContext = new DefaultHttpContext
        {
            Items = new Dictionary<object, object>
            {
                { "Name", "B"}
            }
        };
        Console.WriteLine($"HttpContext before await in FooAsync: {_httpContextAccessor.HttpContext.Items["Name"]}");
        await Task.Delay(1000);
        Console.WriteLine($"HttpContext after await in FooAsync: {_httpContextAccessor.HttpContext.Items["Name"]}");
    }
}

輸出結(jié)果:文章來源地址http://www.zghlxwxcb.cn/news/detail-498035.html

HttpContext before await FooAsync in Main: A
HttpContext before await in FooAsync: B
HttpContext after await in FooAsync: B
HttpContext after await FooAsync in Main: 

到了這里,關(guān)于.NET的AsyncLocal用法指南的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 清除ExecutionContext,阻止 AsyncLocal 在異步流、Thread中傳遞

    清除ExecutionContext,阻止 AsyncLocal 在異步流、Thread中傳遞

    前言: 自從使用了?AsyncLocal 后,就發(fā)現(xiàn)?AsyncLocal 變量像個臭蟲一樣,在有?AsyncLocal 變量的線程中啟動的 Task 、或者 Thread 都會附帶?AsyncLocal 變量 。 在項目使用?AsyncLocal 實現(xiàn)了全局、局部 工作單元 ,但是就無法在后續(xù)作業(yè)中開啟多個線程了(需求就是要開啟多個線程,俺

    2024年02月05日
    瀏覽(30)
  • ElasticSearch簡介及常見用法

    Elasticsearch 是 Elastic Stack 核心的分布式搜索和分析引擎。 Logstash 和 Beats 有助于收集、聚合和豐富您的數(shù)據(jù)并將其存儲在 Elasticsearch 中。 Kibana 使您能夠以交互方式探索、可視化和分享對數(shù)據(jù)的見解,并管理和監(jiān)控堆棧。 Elasticsearch 可以快速 索引、搜索和分析 海量數(shù)據(jù)。 Ela

    2024年03月20日
    瀏覽(17)
  • C++ final用法簡介

    如下代碼: B 繼承A ,編譯以及運行都正常。 如果我們現(xiàn)在把第三行由: 修改為 編譯錯誤如下: 由于 class A13 之后有加 final ,表明這個類不能被繼承,所以編譯報錯。 最終全部代碼如下: 如下代碼B14繼承A14,編譯運行都正常, 如果我們將虛函數(shù) 后面加 final: 則編譯錯誤

    2024年02月13日
    瀏覽(19)
  • XML 簡介及用法詳解

    XML 是一種用于存儲和傳輸數(shù)據(jù)的與軟件和硬件無關(guān)的工具。 XML代表 eXtensible Markup Language(可擴展標記語言) 。XML是一種與HTML非常相似的標記語言。XML被設(shè)計用于存儲和傳輸數(shù)據(jù)。XML被設(shè)計成具有自我描述性。XML不執(zhí)行任何操作,也許有點難理解,但XML不執(zhí)行任何操作。 這

    2024年03月15日
    瀏覽(26)
  • golang的os包用法簡介

    Go語言的 os 包中提供了操作系統(tǒng)函數(shù)的接口,是一個比較重要的包。顧名思義,os 包的作用主要是在服務(wù)器上進行系統(tǒng)的基本操作,如文件操作、目錄操作、執(zhí)行命令、信號與中斷、進程、系統(tǒng)狀態(tài)等等。 Hostname 函數(shù)定義: Hostname 函數(shù)會返回內(nèi)核提供的主機名。 Environ 函數(shù)定

    2024年02月03日
    瀏覽(17)
  • JavaSrcipt之this的用法簡介

    在JavaScript中,this是一個非常重要的概念,它表示當前執(zhí)行上下文中的對象。在不同的上下文和函數(shù)中,this的值是不同的。讓我們來詳細介紹this的用法: 1)全局上下文中 在全局上下文中(即不在任何函數(shù)內(nèi)部),this指向全局對象,在瀏覽器中,通常指向wind

    2024年02月06日
    瀏覽(23)
  • golang中fallthrough簡介及用法

    fallthrough是golang中的一個,它用于在switch語句中控制代碼的執(zhí)行流程。通常情況下,當一個case分支匹配成功后,switch語句就會結(jié)束,不會繼續(xù)執(zhí)行后面的case分支。但是,如果在一個case分支的最后加上fallthrough,那么switch語句就會繼續(xù)執(zhí)行下一個case分支,無論下

    2024年03月12日
    瀏覽(17)
  • Lambda表達式:簡介、語法和用法

    Lambda表達式:簡介、語法和用法

    Lambda表達式是Java 8中引入的一個重要特性,它允許開發(fā)者以更加簡潔的方式編寫函數(shù)式代碼。在本文中,我們將深入探討Lambda表達式的概念、語法和用法,并為每個實例提供代碼演示,同時對比與傳統(tǒng)方法的區(qū)別和優(yōu)勢。 Lambda表達式是一種匿名函數(shù),它主要用于表示簡單的行

    2023年04月19日
    瀏覽(34)
  • L298N——簡介及用法

    L298N——簡介及用法

    1. 簡介 L298N是意法半導體集團旗下量產(chǎn)的一種電機驅(qū)動芯片,擁有工作電壓高、輸出電流大、驅(qū)動能力強、發(fā)熱量低、抗干擾能力強等特點,通常用來驅(qū)動繼電器、螺線管、電磁閥、直流電機以及步進電機。 2. 各接口的作用及使用方法 如圖所示,左右兩端分別連接并控制電

    2024年02月11日
    瀏覽(15)
  • .NET 簡介

    .NET 是一種用于構(gòu)建多種應(yīng)用的免費開源開發(fā)平臺,使用類庫在不同應(yīng)用和應(yīng)用類型中共享功能。 使用 .NET 時,無論正在構(gòu)建哪種類型的應(yīng)用,代碼和項目文件看起來都一樣。 可以訪問每個應(yīng)用的相同運行時、API 和語言功能。 跨平臺,可以為許多操作系統(tǒng)創(chuàng)建 .NET 應(yīng)用,通

    2024年02月04日
    瀏覽(14)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包