【导读】最近查看有关框架源码,发现AsyncLocal这玩意水还挺深,于是花了一点功夫去研究,同时对比ThreadLocal说明二者区别以及在何时场景下使用AsyncLocal或ThreadLocal

ThreadLocal相信很多童鞋用过,但AsyncLocal具体使用包括我在内的一大部分童鞋应该完全没怎么使用过。

AsyncLocal同样出现在.NET Framework 4.6+(包括4.6),当然在.NET Core中没有版本限制即CoreCLR,对此类官方所给的解释是:将本地环境数据传递到异步控制流,例如异步方法

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

官方概念解释在我们初次听来好像还是有点抽象,不打紧,接下来我们通过实际例子来进行详细说明和解释

AsyncLocal和ThreadLocal区别

首先我们先看如下例子,然后再分析二者和什么有关系

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();
}

猜猜如上将会打印出什么结果呢?

为何ThreadLocal所打印的值为空值呢?我们不是设置了值吗?此时我们将要从执行环境开始说起

若完全理解ExecutionContext与SynchronizationContext二者概念和关系,理论上来讲则可解答出上述问题,这里我们简单叙述下,更详细介绍请查阅相关资料自行了解

ExecutionContext俗称“执行上下文”,也就是说和“环境”信息相关,这也就意味着它存储着和我们当前程序所执行的环境相关的数据,这类环境信息数据存储在ThreadStatic或ThreadLocal中,换句话说ThreadLocal和特定线程相关

上述我们讨论的是相同环境或上下文中,若是不同上下文即不同线程中,那情况又该如何呢?

在异步操作中,在某一个线程中启动操作,但却在另一线程中完成,此时我们将不能利用ThreadLocal来存储数据,因线程切换所需存储数据,我们可以称之为环境“流动”

对于逻辑控制流,我们期望的是执行环境相关数据能同控制流一起流动,以便能让执行环境相关数据能从一个线程移动到另外一个线程,ExecutionContext的作用就在于此。而SynchronizationContext是一种抽象,比如Windows窗体则提供了WindowsFormSynchronizationContext上下文等等

SynchronizationContext作为ExecutionContext执行环境的一部分

ExecutionContext是当前执行环境,而SynchronizationContext则是针对不同框架或UI的抽象

我们可通过SynchronizationContext.Current得到当前执行环境信息。

到这里想必我们已经明白基于特定线程的ThreadLocal在当前线程设置值后,但await却不在当前线程,所以打印值为空,若将上述第一个await去除,则可打印出设置值,而AsyncLocal却是和执行环境相关,也就是说与线程和调用堆栈有关,并不针对特定线程,它是流动的。

AsyncLocal原理初步分析

首先我们通过一个简单的例子来演示AsyncLocal类中值变化过程,我们能从表面上可得出的结论,然后最终结合源码进行进一步分析

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方法内部将其值进行了修改并打印出修改过后的结果,在Task结束后,最终打印的却是初始值。

在Task方法内部修改其值,但在任务结束后仍为初始值,这是一种“写时复制”行为,AsyncLocal内部做了两步操作

进行AsyncLocal实例的拷贝副本,但这是浅复制行为而非深复制

在设置新的值之前完成复制操作

接下来我们再通过一个层层调用例子并深入分析

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方法内部打印值为空,因为在Demo2方法内部并未使用异步,所以能打印出所设置的值,这说明如下问题

每次进行实际的aysnc/await后,都会启动一个新的异步上下文,并且该上下文与父异步上下文完全隔离且独立,换句话说,在异步方法内,可查询自己所属AsyncLocal<T>,以便能确保不会污染父异步上下文,因为所做更改完全是针对当前异步上下文的本地内容

至于为何在Demo1方法内部打印为空,想必我们已经很清晰,当async方法返回时,返回的是父异步上下文,此时将看不到任何子异步上下文所执行的修改。

AsyncLocal原理源码分析

我们来到AsyncLocal类,通过属性Value设置值,内部通过调用ExecutionContext类中的SetLocalValue方法进行设置,源码如下:

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);}
}

当首次设置值时,我们通过Thread.CurrentThread.ExecutionContext,获取其属性将为空,通过AsyncLocalValueMap.Create创建一个AsyncLocal实例并设置值

同时我们也可以看到,若在同一执行环境中,当前最新设置值与之前所设置值相同,此时将不会是覆盖,而是直接返回。

我们直接来到最后如下几行代码:

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

若默认使用Task默认线程池调度,即使线程池重用线程,其执行环境上下文也会不同,如此可说明将更能保证不会将线程数据泄露到另外一个线程中,也就是说在重用线程时,但将会保证异步本地实例会按照预期进行GC(个人以为,理论上情况应该是这样,这样也能保证AsyncLocal是安全的)。

至于其他关于如何进行值更改后事件通知,这里就不再额外展开叙述

由于AsyncLocal使用浅拷贝,我们应保证存储的数据类型不可变,若要修改AsyncLocal<T>实例值,必须保证异步上下文隔离且相互不会影响。

到这里我们已完全清楚,AsyncLocal是针对异步控制流的良好支持,且数据可流动,当前线程AsyncLocal实例所存储的数据可流动到异步任务控制流中的默认任务调度线程池的线程中

当然我们也可以调用如下执行环境上下文中的抑制流动方法来禁用数据流动

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();
}

此时在其任务内部打印的值将为空。最后,我们再来对AsyncLocal做一个最终总结

???? AsyncLocal出现于.NET Framework 4.6+(包含4.6)、CoreCLR

???? AsyncLocal是每个ExecutionContext实例的一个变量,它并非如同ThreadLocal基于特定线程的持久化数据存储

???? 若需要基于本地环境的异步控制流,使用AsyncLocal而非ThreadLocal,在线程池中重用线程时,ThreadLocal会保留之前值(基于理论猜测),而AsyncLocal不会

???? AsyncLocal在每次asyn/await后,都将重新生成一个新的异步执行上下文环境,父异步上下文执行环境和子异步上下文执行环境完全隔离且互不影响

???? AsyncLocal进行异步控制流时,由于内部对数据进行浅拷贝,确保其实例类型参数应为不可变数据类型

浅谈AsyncLocal,我们应该知道的那些事儿相关推荐

  1. 浅谈MySQL存储引擎-InnoDBMyISAM

    浅谈MySQL存储引擎-InnoDB&MyISAM 存储引擎在MySQL的逻辑架构中位于第三层,负责MySQL中的数据的存储和提取.MySQL存储引擎有很多,不同的存储引擎保存数据和索引的方式 ...

  2. 【大话设计模式】——浅谈设计模式基础

    初学设计模式给我最大的感受是:人类真是伟大啊!单单是设计模式的基础课程就让我感受到了强烈的生活气息. 个人感觉<大话设计模式>这本书写的真好.让貌似非常晦涩难懂的设计模式变的生活化.趣味化 ...

  3. 学校计算机机房好处,浅谈学校计算机机房维护

    浅谈学校计算机机房维护    现在的学校机房都配置了数量较多的计算机,而且机房的使用非常频繁.对于怎样维护好计算机,特别是计算机软件系统,对广大计算机教师来说是一个很重要且非常现实的问题.下面就本人在 ...

  4. java 中的单元测试_浅谈Java 中的单元测试

    单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...

  5. mybatis与php,浅谈mybatis中的#和$的区别

    浅谈mybatis中的#和$的区别 发布于 2016-07-30 11:14:47 | 236 次阅读 | 评论: 0 | 来源: 网友投递 MyBatis 基于Java的持久层框架MyBatis 本 ...

  6. 浅谈GCC预编译头技术

    浅谈GCC预编译头技术 文/jorge --谨以此文,悼念我等待MinGW编译时逝去的那些时间. 其 实刚开始编程的时候,我是丝毫不重视编译速度之类的问题的,原因很简单,因为那时我用BASICA.后来 ...

  7. 【笔记】震惊!世上最接地气的字符串浅谈(HASH+KMP)

    震惊!世上最接地气的字符串浅谈(HASH+KMP) 笔者过于垃圾,肯定会有些错的地方,欢迎各位巨佬指正,感激不尽! 引用:LYD的蓝书,一本通,DFC的讲稿,网上各路巨佬 Luguo id: 章鱼那个 ...

  8. 浅谈几种区块链网络攻击以及防御方案之其它网络攻击

    旧博文,搬到 csdn 原文:http://rebootcat.com/2020/04/16/network_attack_of_blockchain_other_attack/ 写在前面的话 自比特 ...

  9. 浅谈几种区块链网络攻击以及防御方案之拒绝服务攻击

    旧博文,搬到 csdn 原文:http://rebootcat.com/2020/04/14/network_attack_of_blockchain_ddos_attack/ 写在前面的话 自比特币 ...

最新文章

  1. Python数据可视化:幂律分布
  2. python语言程序设计西安电子科技大学答案-徐悦甡 | 个人信息 | 西安电子科技大学个人主页...
  3. python识别验证码ocr_Python 验证码识别-- tesserocr
  4. HDR【openCV实现】
  5. 这就是数据分析之数据变换
  6. hdu 4300 Clairewd’s message kmp匹配! 多校联合赛第一题
  7. stm32无源蜂鸣器定时器_【STM32H7教程】第20章 STM32H7的GPIO应用之无源蜂鸣器...
  8. 服务器的mdf文件怎么打开,mdf文件用什么打开 mdf文件怎么打开
  9. asp.net 利用DirectoryEntry来验证用户以及开机密码
  10. Multiple Hypothesis Tracking Revisited
  11. word表格一行不对齐解决方法
  12. Java关键字详解-配视频讲解链接(附带一些面试题)
  13. 全球最受欢迎电商平台有哪些?这些平台怎么快速增加销量?
  14. 福州黑白风格的婚纱照拍摄介绍
  15. 数字藏品|NFT整个行业的调研报告 数字藏品有价值吗 数字藏品是什么 百度数字藏品
  16. 计算机基础知识(二)
  17. 怎样将本地图片转换成网络链接图片
  18. 座舱人机交互「暗潮汹涌」,语音「下」,多模态「上」
  19. 微信小程序蓝牙打印二维码
  20. 计算机无法卸载软件,电脑软件无法卸载怎么解决

热门文章

  1. codevs原创抄袭题 5960 信使
  2. wikioi 1034 家 实时动态的网络流量(费用流)
  3. java高级类_Java高级类特性(一)
  4. 可编程交换时代就在这里
  5. hivesql优化的深入解析
  6. 你不知道的JavaScript(二)
  7. Codeforces Round #358 (Div. 2) A. Alyona and Numbers 水题
  8. 生成二维码的 jQuery 插件:jquery.qrcode.js
  9. 使用chpasswd命令批量修改系统用户密码
  10. 在web网页中正确使用图片格式