狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

.NET AsyncLocal 避坑指南

freeflydom
2024年2月27日 9:31 本文熱度 630

AsyncLocal 用法簡介

通過 AsyncLocal 我們可以在一個(gè)邏輯上下文中維護(hù)一份私有數(shù)據(jù),該上下文后續(xù)代碼中都可以訪問和修改這份數(shù)據(jù),但另一個(gè)無關(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 實(shí)現(xiàn)原理

在我之前的博客 揭秘 .NET 中的 AsyncLocal 中深入介紹了 AsyncLocal 的實(shí)現(xiàn)原理,這里只做簡單的回顧。

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

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

  • new Thread(ThreadStart start).Start()

  • Task.Run(Action action)

  • ThreadPool.QueueUserWorkItem(WaitCallback callBack)

  • await 之后

以 await 為例,當(dāng)我們在一個(gè)方法中使用了 await 關(guān)鍵詞,編譯器會(huì)將這個(gè)方法編譯成一個(gè)狀態(tài)機(jī),這個(gè)狀態(tài)機(jī)會(huì)在 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!


AsyncLocal 的坑

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

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

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

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

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

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

AsyncLocal 的避坑指南

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

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

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

我們還需要設(shè)計(jì)一個(gè) 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 的實(shí)現(xiàn)原理

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

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

不過 HttpContextAccessor 中的邏輯有點(diǎn)特殊,它的 HttpContextHolder 是為保證清除 HttpContext 時(shí),這個(gè) HttpContext 能在所有引用它的 ExecutionContext 中被清除(可能因?yàn)樾薷?HttpContextHolder 之外的 AsyncLocal 數(shù)據(jù)導(dǎo)致 ExecutionContext 已經(jīng) COW 很多次了)。

下面是 HttpContextAccessor 的實(shí)現(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 時(shí),這個(gè) 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 的值,所以會(huì)導(dǎo)致 ExecutionContext 的 COW。新的 HttpContext 不會(huì)被傳遞到原先的 ExecutionContext 中。

                _httpContextCurrent.Value = new HttpContextHolder { Context = value };

            }

        }

    }


    private sealed class HttpContextHolder

    {

        public HttpContext? Context;

    }

}

但 HttpContextAccessor 的實(shí)現(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é)果:

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: 


轉(zhuǎn)自博客園https://www.cnblogs.com/eventhorizon/p/17170301.html



該文章在 2024/2/27 9:31:43 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲(chǔ)管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved