asynclocal 是 executioncontext 中槽位的封装,其值依赖 executioncontext 的自动流动,赋值会触发上下文复制,禁用流动或上下文丢失将导致读取 default(t)。

ExecutionContext 是 AsyncLocal 的运行时载体
AsyncLocalExecutionContext 中一个“槽位”(slot)的封装。每次读写 AsyncLocal<t>.Value</t>,实际是在当前 ExecutionContext 的上下文中读写其内部维护的一个 Dictionary<asynclocal object></asynclocal> 映射。没有 ExecutionContext 的流转,AsyncLocal 就无法跨异步边界延续值。
默认情况下 ExecutionContext 会自动流动(flow),但可被禁用
当调用 await、Task.Run、ThreadPool.QueueUserWorkItem 等操作时,.NET 默认会捕获并传递当前 ExecutionContext(含其中所有 AsyncLocal 值)。但以下情况会中断流动:
- 显式调用
ExecutionContext.SuppressFlow()后进入异步分支 - 使用
Task.Factory.StartNew(..., TaskCreationOptions.DenyChildAttach)且未传入捕获的上下文 - 在 ASP.NET Core 2.1+ 中,
HttpContext不再自动绑定到AsyncLocal,需手动存取(如通过IHttpContextAccessor)
一旦流动被抑制,新任务/线程中的 AsyncLocal.Value 将是默认值(default(T)),而非父上下文的副本。
AsyncLocal.Value 的赋值会触发 ExecutionContext 的复制
对 AsyncLocal<t>.Value</t> 赋值时,如果当前 ExecutionContext 处于“只读”状态(例如刚从线程池回调中恢复),运行时会先 shallow-copy 当前 ExecutionContext,再更新其中该 AsyncLocal 对应的值。这意味着:
- 频繁赋值可能带来小量分配开销(尤其在高并发异步循环中)
- 多个
AsyncLocal实例之间互不影响,各自独立维护 slot - 值类型会被装箱存入
ExecutionContext的对象字典,注意避免意外装箱
var local = new AsyncLocal<int>(); local.Value = 42; // 触发 ExecutionContext 检查与可能的复制 Console.WriteLine(local.Value); // 输出 42
不要依赖 AsyncLocal 在 finalizer 或 unobserved Task 中存活
AsyncLocal 的生命周期严格绑定于 ExecutionContext 的存在周期。而 ExecutionContext 可能在以下场景被提前丢弃:
- 未 await 的
Task被 GC 回收(尤其Task进入 faulted/canceled 状态后) - 使用
ValueChanged事件注册回调,但回调执行时原上下文已退出(比如 Web 请求结束) - 在
async void方法中修改AsyncLocal,后续无 await 点,上下文很快失效
这类场景下读取 AsyncLocal.Value 很可能得到 default(T) 或旧值,且无任何异常提示——这是最隐蔽也最难调试的问题之一。









