>>返回《C# 并发编程》html
[ThreadStatic]
特性、ThreadLocal<T>
、CallContext
、AsyncLocal<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
结论:async
平常开发过程当中,咱们常常遇到异步的场景。性能
异步可能会致使代码执行线程的切换。测试
例如:线程
测试:[ThreadStatic]
特性、ThreadLocal<T>
、AsyncLocal<T>
,三种共享变量被异步代码赋值后的表现。code
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
还可以保留原来的值.
咱们总结一下这些变量的表现:
实现方式 | 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(...)
默认不是新建一个线程,而是使用线程池线程AsyncLocal<T>
的 Value 属性的真正的数据存取是经过 ExecutionContext 的 internal
的方法 GetLocalValue
和 SetLocalValue
将数据存到 当前ExecutionContext 上的 m_localValues
字段上
new Thread(...).Start()
new Task(...).Start()
Task.Run(...)
ThreadPool.QueueUserWorkItem(...)
await
语法糖m_localValues
类型是 IAsyncLocalValueMap
如下为基础设施提供的实现:
类型 | 元素个数 |
---|---|
EmptyAsyncLocalValueMap | 0 |
OneElementAsyncLocalValueMap | 1 |
TwoElementAsyncLocalValueMap | 2 |
ThreeElementAsyncLocalValueMap | 3 |
MultiElementAsyncLocalValueMap | 4 ~ 16 |
ManyElementAsyncLocalValueMap | > 16 |
随着 ExecutionContext 所关联的 AsyncLocal 数量的增长, IAsyncLocalValueMap 的实现将会在 ExecutionContext
的 SetLocalValue
方法中被不断替换。
AsyncLocal
类型存储数据,是在本身线程的 ExecutionContext 中IAsyncLocalValueMap
类型的变量中,此变量会根据存储的 AsyncLocal
变量个数而切换实现
参考资料:
《浅析 .NET 中 AsyncLocal 的实现原理》 --- 黑洞视界