从 ThreadLocal 到 AsyncLocal

前些天跟大佬们在群里讨论如何在不使用构造函数,不增加方法参数的情况下把一个上下文注入到方法内部使用,得出的结论是 AsyncLocal 。感叹自己才疏学浅,居然才知道有 AsyncLocal 这种神器。于是赶紧恶补一下。

ThreadLocal

要说 AsyncLocal 还得先从 ThreadLocal 说起。ThreadLocal 封装的变量,可以在线程间进行隔离。不同线程对同一个变量的修改只在当前线程有效。这个应该大家都比较熟悉不多说了。下面简单演示一下:threadLocal 初始值为1,然后启动多个线程对这个变量进行修改,最后主线程等待1秒,保证其它线程都执行成功后再次打印threadLocal的值。

ThreadLocal<int> threadLocal = new ThreadLocal<int>();
threadLocal.Value = 1;
Console.WriteLine("thread id {0} value:{1} START", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);new Thread(() => {threadLocal.Value = 2;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
}).Start();
new Thread(() => {threadLocal.Value = 3;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
}).Start();
new Thread(() => {threadLocal.Value = 4;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
}).Start();
new Thread(() => {threadLocal.Value = 5;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
}).Start();
new Thread(() => {threadLocal.Value = 6;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
}).Start();Thread.Sleep(1000);
Console.WriteLine("thread id {0} value:{1} END", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);Console.Read();

输出:

Hello, World!
thread id 1 value:1 START
thread id 7 value:2
thread id 8 value:3
thread id 9 value:4
thread id 10 value:5
thread id 11 value:6
thread id 1 value:1 END

通过一系列线程修改后 threadLocal 的值在 1 号线程始终为 1 ,这也符合我们对 ThreadLocal 预期。

当 ThreadLocal 遇到 await

上面的示例我们使用的是 new Thread 的办法进行多线程操作,现在这种做法已经很少见了。我们现在更多的时候会使用 async/await Task 来帮我们做多线程异步操作。这个时候我们的 ThreadLocal 就会力不从心了,让我们改造一下代码:我们把 new Thread 全部改造成 Task.Run 来执行修改变量的操作。

ThreadLocal<int> threadLocal = new ThreadLocal<int>();
threadLocal.Value = 1;
Console.WriteLine("thread id {0} value:{1} START", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);await Task.Run(() => {threadLocal.Value = 2;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
});
await Task.Run(() => {threadLocal.Value = 3;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
});
await Task.Run(() => {threadLocal.Value = 4;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
});
await Task.Run(() => {threadLocal.Value = 5;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
});
await Task.Run(() => {threadLocal.Value = 6;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);
});
Console.WriteLine("thread id {0} value:{1} END", Thread.CurrentThread.ManagedThreadId, threadLocal.Value);Console.Read();

输出:

Hello, World!
thread id 1 value:1 START
thread id 7 value:2
thread id 8 value:3
thread id 10 value:4
thread id 11 value:5
thread id 12 value:6
thread id 11 value:5 END

通过输出我们可以看到 START 跟 END 的输出已经不一样了。至于为什么,如果理解 Task 的原理,其实也很好理解。简单来说,Task 的异步是一种基于状态机实现方式,编译器碰到 await 会把代码编译成一个代码块,表示一种状态。Task 的任务调度器会调度空闲线程去处理每一个状态。当一个状态完成后,调度器调度一个空闲线程去处理下一个任务,这样一个接一个处理。这里最大的困扰其实是主观上的当前线程(打印 START 跟 END 的线程)已经不是同一个了,打印 START 的是 1 号线程,打印 END 的是 11 号线程,那么 ThreadLocal 自然不适合这种场景了。

AsyncLocal

上面我们已经知道 ThreadLocal 已经不适合在新的 TPL 模型下的多线程变量隔离。那么我们该如何进行应对呢?答案就是 AsyncLocal 。
让我们改造下代码,把 Threadlocal 替换成 AsyncLocal ,其它不变,运行一下代码。

AsyncLocal<int> asyncLocal = new AsyncLocal<int>();
asyncLocal.Value = 1;
Console.WriteLine("thread id {0} value:{1} START", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);await Task.Run(() => {asyncLocal.Value = 2;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);
});
await Task.Run(() => {asyncLocal.Value = 3;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);
});
await Task.Run(() => {asyncLocal.Value = 4;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);
});
await Task.Run(() => {asyncLocal.Value = 5;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);
});
await Task.Run(() => {asyncLocal.Value = 6;Console.WriteLine("thread id {0} value:{1}", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);
});
Console.WriteLine("thread id {0} value:{1} END", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);Console.Read();

输出:

thread id 1 value:1 START
thread id 6 value:2
thread id 7 value:3
thread id 8 value:4
thread id 11 value:5
thread id 7 value:6
thread id 7 value:1 END

结果如我们所愿, START 跟 END 的值是一致的。我们可以看到虽然线程发生了切换,但是值被很好的保留在了当前流程下。
让我们使用另外一个代码实例来演示下 AsyncLocal 的特性。上面的代码演示的是一个 Task 接一个 Task 的场景,一下我们演示下 Task 嵌套 Task 的场景。

AsyncLocal<int> asyncLocal = new AsyncLocal<int>();
//block 1
asyncLocal.Value = 1;
Console.WriteLine("thread id {0} value:{1} START", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);await Task.Run(async () =>{//block 2asyncLocal.Value = 2;Console.WriteLine("thread id {0} value:{1} ", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);await Task.Run(() => {//block 3asyncLocal.Value = 3;Console.WriteLine("thread id {0} value:{1} ", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);});Console.WriteLine("thread id {0} value:{1} ", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);});Console.WriteLine("thread id {0} value:{1} END", Thread.CurrentThread.ManagedThreadId, asyncLocal.Value);Console.Read();

输出

thread id 1 value:1 START
thread id 6 value:2
thread id 7 value:3
thread id 7 value:2
thread id 7 value:1 END

跟你预期的结果一致吗?结果为:1 2 3 2 1 。AsyncLocal 的变量值会被隔离在每个 Task 流程内,就算嵌套,子流程对变量的修改也不会影响到父流程的值。

AsyncLocal 实用

AsyncLocal 的特性说的差不多了。那么 AsyncLocal 到底该使用在什么场景呢?
当我们重构代码的时候如果需要把一个上下文参数传递进去,最傻瓜的办法就是在所有的调用类的构造函数上加入这个参数,或者在所有的方法调用上加入这个参数。但是这种办法是破坏性比较大的,因为函数签名被破坏意味着接口(广义上)约束被破坏了。这个时候我们可以通过 AsyncLocal 把上下文传递进去。
定义一个 MyContext 类:

public class MyContext : IDisposable{static AsyncLocal<MyContext> _scope = new AsyncLocal<MyContext>();public MyContext(object val){Value = val;_scope.Value = this;}public object Value { get;}public static MyContext? Current{get{return _scope.Value;}}public void Dispose(){if (Value != null){(Value as IDisposable)?.Dispose();}}}

假设我们已经有了 Func1 方法,现在在不破坏任何接口约束的情况下可以把 MyContext 直接通过静态变量 MyContext.Current 获取到。

void Func1()
{Console.WriteLine(MyContext.Current?.Value);
}using (var ctx = new MyContext("context 1"))
{Func1();
}using (var ctx = new MyContext("context 2"))
{Func1();
}
using (var ctx = new MyContext("context 3"))
{await Task.Run(Func1);await Task.Run(Func1);await Task.Run(Func1);
}

另外一个实现其实是大家非常常见的 HttpContextAccessor 。ASP.NET Core 下我们获取 HttpContext 会通过 HttpContextAccessor 获取。HttpContextAccessor 通常被注册为单例。大家有没有想过为啥单例的 HttpContextAccessor.HttpContext 变量不会被多线程或者异步方法打乱?原因也就在于 AsyncLocal 。源码在这 HttpContextAccessor ,并不复杂大家可以看看。

关注我的公众号一起玩转技术

5ad8f0f227978271d3db960d70100457.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295171.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sersync+Rsync 增量实时同步

准备环境&#xff1a; rsync服务器&#xff1a;172.16.3.21sersync2服务器&#xff1a;172.16.3.23一、 rsync服务器先把原来的rsync包删掉yum remove rsync-2.6.8-3.1 -y,然后到http://rsync.samba.org/下载rsync包我用的包如下图所示利用securecrt上传本地[rootacong tongbu1…

浅谈RBF函数

所谓径向基函数 (Radial Basis Function 简称 RBF), 就是某种沿径向对称的标量函数。 通常定义为空间中任一点x到某一中心xc之间欧氏距离的单调函数 , 可记作 k(||x-xc||), 其作用往往是局部的 , 即当x远离xc时函数取值很小。 最常用的径向基函数是高斯核函数 ,形式为 k(||x-xc…

Repeater嵌套绑定Repeater

cs代码&#xff1a; private void RpTypeBind() {//GetQuestionTypeAndCount() 返回一个datatable this.rptypelist.DataSource LiftQuestionCtr.GetQuestionTypeAndCount(); this.rptypelist.DataBind(); } …

字符串之变形数

字符串之变形数 题目:给定2个字符串str1和str2,如果str1和str2出现的字符种类和每个字符出现的个数也一样,那么str1与str2互为变形数。请用函数实现。 例子:str1="123", str2="231" 返回 true 例子:str1="123", str2="2311" 返回…

一个彻底改变世界的思想

全世界只有3.14 % 的人关注了爆炸吧知识因随机性的到来阔然开朗蒙特卡罗赌场蒙特卡罗&#xff08;Monte Carlo&#xff09;是摩纳哥公国&#xff08;Principality of Monaco&#xff09;的一座城市。摩纳哥公国坐落在法国的东南方&#xff0c;总面积为2.02平方公里&#xff0c;…

单元测试 | 如何在Mock时匹配匿名类型参数

前言假设&#xff0c;我们需要单元测试如下方法:public User GetUser(int id) {return Repository.Query<User>(new { ID id }).FirstOrDefault(); }而对于Repository.Query&#xff0c;我们需要进行Mock。怎么实现&#xff1f;不匹配具体参数值如果我们不关心参数的具体…

java中imp结尾,imp java

适配器模式之桥梁模式设计模式目录 http://blog.csdn.net/fenglailea/article/details/52733435风.fox桥梁模式 Bridge Pattern将抽象和实现解耦&#xff0c;使得两者可以独立地变化通用类图组成抽象化角色 Abstraction...文章风来了2016-10-06972浏览量expOracle数据导入导出i…

Cookies

一&#xff0c;前言 Cookies想必所有人都了解&#xff0c; 但是未必所有人都精通。本文讲解了Cookies的各方面知识。 二&#xff0c;基础知识 1.什么是Cookies Cookie 是一小段文本信息&#xff0c;伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问…

用字符串表达式访问JSON数据(java,fastjson)

2019独角兽企业重金招聘Python工程师标准>>> //单元科技-www.ccell.com.cn 技术部,开源 //XML数据有XPATH 如"root/rows[id1]/name" //在JS中JSON数据可以对象方式访问 //java中怎么 用字符串表达式访问JSON数据? 找了很久没有找到,自己写一个,以减小代…

C语言学习

时间&#xff1a;20160302 (1) C语言中&#xff0c;很多标准头文件&#xff0c;这样的头文件好处在于&#xff0c;所有的实现方法都在头文件中进行定义&#xff0c;后期对方法进行修改时&#xff0c;可以只需要改动头文件&#xff0c;就可以了&#xff0c;其他的地方也需要对应…

怎样分辨谁才是朋友圈里的真·贵族?

不是我不明白&#xff0c;这世界变化快但在这个看1分钟视频都要2倍速的世界里有一群人却仍然愿意每天花5分钟阅读2000字以上的文字信息这是多么高贵的一种精神所以高贵的你&#xff0c;值得更好的内容今天我们精选出8个优质公号他们有趣有料、有思想有温度绝对值得你的关注长按…

Android之如何解决Android studio运行出现的HAX kernel modules is not installed

HAX kernel modules is not installed 很久之前,我安装了Android studio,运行时控制台打印出这个,emulator:ERROR:x86 emulation currently requires hardware acceleration! Please ensure Intel HAXM is properly installed and useble, CPU acceleration statis:HAX kern…

Elastic AMP监控.NET程序性能

什么是Elastic AMPElastic APM 是一个应用程序性能监控系统。它可以请求的响应时间、数据库查询、对缓存的调用、外部 HTTP 请求等的详细性能信息&#xff0c;可以实时监控软件服务和应用程序。这可以帮助我们快速查明和修复性能问题。Elastic APM 还会自动收集未处理的错误和异…

java分页的工具类,java分页工具类

PageBean-分页package com.soyea.util;import java.io.Serializable;import java.util.List;/*** 分页工具类*/public class PageBean implements Serializable {private static final long serialVersionUID -8741766802354222579L;private int pageSize5; // 每页显示多少条…

xxx定律-poj-3782

xxx定律 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1639 Accepted Submission(s): 1303 Problem Description 对于一个数n&#xff0c;如果是偶数&#xff0c;就把n砍掉一半&#xff1b;如果是奇数&#x…

asp.net 检测访问者是iphone,android,web(摘录)

aps.net 可以使用WebFormViewEngine来实现。 参考文章&#xff1a;http://www.hanselman.com/blog/MixMobileWebSitesWithASPNETMVCAndTheMobileBrowserDefinitionFile.aspx 源代码 C#代码 public class MobileCapableWebFormViewEngine : WebFormViewEngine { public…

在Android NDK中使用OpenSSL

从 6.0 开始&#xff0c;Google 要求不要使用系统的 OpenSSL&#xff0c;请见&#xff1a;https://developer.android.com...。因此&#xff0c;请不要再使用本文介绍的方法&#xff0c;请自行交叉编译 OpenSSL 或者使用别人编译好的版本。2017年3月注 由于Java较为容易被反编译…

字符串之数字子串求和

题目:给定一个字符串str,求其中全部数字串所代表的数字之和 要求: 1、忽略小数点字符,例如“A1.3”,其中包括2个数字1和3 2、如果是紧贴数字子串的左侧出现字符“-”,当连续出现的数量为奇数时,则数字视为负,连续出现的数量为偶数的则 数字视为正,例如,“A-1…

买的情侣裤衩寄前男友家了,怎么办?

1 那你是要和别人一起去吗&#xff01;▼2 我马上想到我一老师了&#xff01;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 当代父母对孩子的期望变化&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 这个小象打破我的观念&#xff1f;▼5 小个子买…

ctf php sql注入,CTF—攻防练习之HTTP—SQL注入(SSI注入)

主机&#xff1a;192.168.32.152靶机&#xff1a;192.168.32.161ssI是赋予html静态页面的动态效果&#xff0c;通过ssi执行命令&#xff0c;返回对应的结果&#xff0c;若在网站目录中发现了.stm .shtm .shtml等&#xff0c;且对应SSL输入没有过滤该网站可能存在SSI注入漏洞ssi…