说一说:ThreadLocal 、CallContext 、AsyncLocal

  1. 简介

    普通共享变量:

    在某个类上用静态属性的方式即可。

    多线程共享变量

    希望能将这个变量的共享范围缩小到单个线程内

    无关系的B线程无法访问到A线程的值;

    ThreadStatic特性、ThreadLocal 、CallContext 、AsyncLocal 都具备这个特性。

    例子:

    由于 .NET Core 不再实现 CallContext,所以下列代码只能在 .NET Framework 中执行

    class Program

    {

    //对照

    private static string _normalStatic;

    ThreadStatic

    private static string _threadStatic;

    private static ThreadLocal _threadLocal = new ThreadLocal();

    private static AsyncLocal _asyncLocal = new AsyncLocal();

    static void Main(string\[\] args)

    {

    Parallel.For(0, 4, _ =>

    {

    var threadId = Thread.CurrentThread.ManagedThreadId;

    复制代码
         var value = $"这是来自线程{threadId}的数据";
         
         _normalStatic = value;
         _threadStatic = value;
         CallContext.SetData("value", value);
         _threadLocal.Value = value;
         _asyncLocal.Value = value;
         
         Console.WriteLine($"Use Normal;                Thread:{threadId}; Value:{_normalStatic}");
         Console.WriteLine($"Use ThreadStaticAttribute; Thread:{threadId}; Value:{_threadStatic}");
         Console.WriteLine($"Use CallContext;           Thread:{threadId}; Value:{CallContext.GetData("value")}");
         Console.WriteLine($"Use ThreadLocal;           Thread:{threadId}; Value:{_threadLocal.Value}");
         Console.WriteLine($"Use AsyncLocal;            Thread:{threadId}; Value:{_asyncLocal.Value}");
     });
    
     Console.Read();

    }

    }

    输出:

    Use Normal; Thread:15; Value:10Use ThreadStatic; Thread:15; Value:15Use Normal; Thread:10; Value:10Use Normal; Thread:8; Value:10Use ThreadStatic; Thread:8; Value:8Use CallContext; Thread:8; Value:8Use ThreadStatic; Thread:10; Value:10Use CallContext; Thread:10; Value:10Use CallContext; Thread:15; Value:15Use ThreadLocal; Thread:15; Value:15Use ThreadLocal; Thread:8; Value:8Use AsyncLocal; Thread:8; Value:8Use ThreadLocal; Thread:10; Value:10Use AsyncLocal; Thread:10; Value:10Use AsyncLocal; Thread:15; Value:15

结论:

Normal 为对照组

Nomal 的 Thread 与 Value 值不同,因为读到了其他线程修改的值

其他的类型,存储的值,在 Parallel 启动的线程间是隔离的

  1. 异步下的共享变量

日常开发过程中,我们经常遇到异步的场景。

异步可能会导致代码执行线程的切换。

例如:

测试:ThreadStatic特性、ThreadLocal 、AsyncLocal ,三种共享变量被异步代码赋值后的表现。

class Program

{

ThreadStatic

private static string _threadStatic;

private static ThreadLocal _threadLocal = new ThreadLocal();

private static AsyncLocal _asyncLocal = new AsyncLocal();

static void Main(string\[\] args)

{

_threadStatic = "set";

_threadLocal.Value = "set";

_asyncLocal.Value = "set";

PrintValuesInAnotherThread();

Console.ReadKey();

}

复制代码
private static void PrintValuesInAnotherThread()
{
    Task.Run(() =>
    {
        Console.WriteLine($"ThreadStatic: {_threadStatic}");
        Console.WriteLine($"ThreadLocal: {_threadLocal.Value}");
        Console.WriteLine($"AsyncLocal: {_asyncLocal.Value}");
    });
}

}

输出:

ThreadStatic: ThreadLocal: AsyncLocal: set

结论:

在异步发生后,线程被切换,只有 AsyncLocal 还能够保留原来的值.

CallContext 也可以实现这个需求,但 .Net Core 没有被实现,这里就不过多说明。

我们总结一下这些变量的表现:

实现方式 DotNetFx DotNetCore 是否支持数据向辅助线程的

ThreadStatic 是 是 否

ThreadLocal 是 是 否

CallContext.SetData(string name, object data) 是 否 仅当参数 data 对应的类型实现了 ILogicalThreadAffinative 接口时支持

CallContext.LogicalSetData(string name, object data) 是 否 是

AsyncLocal 是 是 是

辅助线程: 用于处理后台任务,用户不必等待就可以继续使用应用程序,比如线程池线程。

注意:

ThreadStatic特性、ThreadLocal 最好不要用在线程池线程

线程池线程是可重用的,线程不会销毁,当线程被重用时,之前使用保存的值依然存在,可能造成影响

使用 AsyncLocal 可以用在线程池线程

线程使用后回归线程池, AsyncLocal 的状态会被清除,无法访问之前的值

new Task(...) 默认不是新建一个线程,而是使用线程池线程

  1. 解析 AsyncLocal

AsyncLocal的 Value 属性的真正的数据存取是通过 ExecutionContext 的 internal 的方法 GetLocalValue 和 SetLocalValue 将数据存到 当前ExecutionContext 上的 m_localValues 字段上

ExecutionContext 会根据执行环境进行流动,详见 《ExecutionContext(执行上下文)综述》

简单描述就是,线程发生切换的时候, ExecutionContext 会在前一个线程中被捕获,流向下一个线程,它所保存的数据也就随之流动了

在所有会发生线程切换的地方,基础类库(BCL) 都为我们封装好了对 ExecutionContext 的捕获

例如:

new Thread(...).Start()

new Task(...).Start()

Task.Run(...)

ThreadPool.QueueUserWorkItem(...)

await 语法糖

m_localValues 类型是 IAsyncLocalValueMap

3.1. IAsyncLocalValueMap 的实现

以下为基础设施提供的实现:

类型 元素个数

EmptyAsyncLocalValueMap 0

OneElementAsyncLocalValueMap 1

TwoElementAsyncLocalValueMap 2

ThreeElementAsyncLocalValueMap 3

MultiElementAsyncLocalValueMap 4 ~ 16

ManyElementAsyncLocalValueMap > 16

随着 ExecutionContext 所关联的 AsyncLocal 数量的增加, IAsyncLocalValueMap 的实现将会在 ExecutionContext 的 SetLocalValue 方法中被不断替换。

查询的时间复杂度和空间复杂度依次递增

3.2. 结论

AsyncLocal 类型存储数据,是在自己线程的 ExecutionContext 中

ExecutionContext 的实例会随着异步或者多线程的启动而被流向执行后续代码的其他线程,保证了启动异步的线程存储的数据可以被访问到

数据存到 IAsyncLocalValueMap 类型的变量中,此变量会根据存储的 AsyncLocal 变量个数而切换实现

支持存储量越大的实现类型,性能越差

捐赠地址:http://www.kalehu.com/?contact/

相关推荐
leo__52011 分钟前
C# 虚拟键盘(软键盘)实现
单片机·c#·计算机外设
周杰伦fans2 小时前
AutoCAD C# 二次开发:如何精确监听工作空间切换事件
前端·c#
用户3721574261352 小时前
如何使用 C# 自动调整 Excel 行高和列宽
c#
AI导出鸭PC端2 小时前
智谱清言怎么生成word文档?AI导出鸭终结乱码烦恼
人工智能·ai·c#·word·豆包·ai导出鸭
terry6003 小时前
从流畅交互到高可用:企讯通Qcaptcha滑动拼图的毫秒级响应与容灾设计
web安全·json·asp.net·信息与通信·数据库架构
xiaoshuaishuai84 小时前
C# AvaloniaUI 中旋转
开发语言·c#
weixin_428005304 小时前
C#调用 AI学习从0开始-第2阶段(Function Calling+工具调用智能体)-第9天实战-实现计算器工具
开发语言·学习·c#·functioncalling·ai实现计算器工具
guygg884 小时前
基于C# + Halcon的通用ROI绘制工具
stm32·单片机·c#
双河子思5 小时前
《代码整洁之道》——读书笔记(持续更新)
开发语言·c++·c#
诙_5 小时前
unity——C#
unity·c#·游戏引擎