>>返回《C# 并发编程》

1. 简介

  • 普通共享变量:

    • 在某个类上用静态属性的方式即可。
  • 多线程共享变量
    • 希望能将这个变量的共享范围缩小到单个线程
    • 无关系的B线程无法访问到A线程的值;

[ThreadStatic]特性、ThreadLocal<T>CallContextAsyncLocal<T> 都具备这个特性。

例子:

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

class Program
{
//对照
private static string _normalStatic; [ThreadStatic]
private static string _threadStatic;
private static ThreadLocal<string> _threadLocal = new ThreadLocal<string>();
private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>();
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:10
Use [ThreadStatic]; Thread:15; Value:15
Use Normal; Thread:10; Value:10
Use Normal; Thread:8; Value:10
Use [ThreadStatic]; Thread:8; Value:8
Use CallContext; Thread:8; Value:8
Use [ThreadStatic]; Thread:10; Value:10
Use CallContext; Thread:10; Value:10
Use CallContext; Thread:15; Value:15
Use ThreadLocal; Thread:15; Value:15
Use ThreadLocal; Thread:8; Value:8
Use AsyncLocal; Thread:8; Value:8
Use ThreadLocal; Thread:10; Value:10
Use AsyncLocal; Thread:10; Value:10
Use AsyncLocal; Thread:15; Value:15

结论:

  • Normal 为对照组
  • Nomal 的 Thread 与 Value 值不同,因为读到了其他线程修改的值
  • 其他的类型,存储的值,在 Parallel 启动的线程间是隔离的

2. 异步下的共享变量

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

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

例如:

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

class Program
{
[ThreadStatic]
private static string _threadStatic;
private static ThreadLocal<string> _threadLocal = new ThreadLocal<string>();
private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>();
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<T> 最好不要用在线程池线程

    • 线程池线程是可重用的,线程不会销毁,当线程被重用时,之前使用保存的值依然存在,可能造成影响
  • 使用 AsyncLocal<T> 可以用在线程池线程
    • 线程使用后回归线程池, AsyncLocal<T> 的状态会被清除,无法访问之前的值
  • new Task(...) 默认不是新建一个线程,而是使用线程池线程

3. 解析 AsyncLocal

  • AsyncLocal<T>Value 属性的真正的数据存取是通过 ExecutionContextinternal 的方法 GetLocalValueSetLocalValue 将数据存到 当前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实现将会在 ExecutionContextSetLocalValue 方法中被不断替换

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

3.2. 结论

  • AsyncLocal 类型存储数据,是在自己线程的 ExecutionContext
  • ExecutionContext 的实例会随着异步或者多线程的启动而被流向执行后续代码的其他线程,保证了启动异步的线程存储的数据可以被访问到
  • 数据存到 IAsyncLocalValueMap 类型的变量中,此变量会根据存储的 AsyncLocal 变量个数而切换实现
    • 支持存储量越大的实现类型,性能越

参考资料

《浅析 .NET 中 AsyncLocal 的实现原理》 --- 黑洞视界

最新文章

  1. [Java]Java日期及时间库插件 -- Joda Time.
  2. ORA-12516:TNS:listener could not find available handler with matching protocol stack
  3. SQL排序
  4. 让 “微软雅黑” 在IE6下完美显示
  5. HDU 1029 Ignatius and the Princess IV --- 水题
  6. hdu3589 Jacobi symbol(二次剩余 数论题)
  7. Android 开发中的屏幕适配技术详解
  8. springmvc jstl
  9. wpf XAML xaml 进行 数据绑定,Resource DataContext ElementName
  10. Less 的使用
  11. Unity访问Access数据库
  12. codeforces#1152D. Neko and Aki&#39;s Prank(dp)
  13. idea怎么创建properties文件
  14. postgresql数据库3种程序(rule,trigger ,FUNCTION )
  15. Linux--vim编辑器和文件恢复
  16. linux下perforce(p4)的使用方法和命令
  17. 通过命名管道协议方式访问群集SQL的一个小问题
  18. iOS项目中的网络请求和上下拉刷新封装
  19. java的一些基本格式
  20. URAL 1944 大水题模拟

热门文章

  1. KMP——强大的next数组
  2. (转) fuzzing XSS filter
  3. Installing PyCharm
  4. 这个时候 快下班了 我来翻译一段: Pro ASP.NET MVC 3 Framework
  5. Windows中安装Linux子系统的详细步骤
  6. Kivy 自定义控件之(一)
  7. 机器学习-浅谈神经网络和Keras的应用
  8. docker扫盲,面试连这都不会就等着挂吧!
  9. Docker底层架构之基础架构
  10. HashMap 详细讲解