AsyncLocal 用法简介

发布时间:2026/7/1 16:41:12

AsyncLocal 用法简介 通过 AsyncLocal 我们可以在一个逻辑上下文中维护一份私有数据该上下文后续代码中都可以访问和修改这份数据但另一个无关的上下文是无法访问的。无论是在新创建的 Task 中还是 await 关键词之后我们都能够访问前面设置的 AsyncLocal 的数据。class Program { private static AsyncLocalstring _asyncLocal new AsyncLocalstring(); static async Task Main(string[] args) { _asyncLocal.Value Hello World!; Task.Run(() Console.WriteLine($AsyncLocal in task: {_asyncLocal.Value})); await FooAsync(); Console.WriteLine($AsyncLocal after await FooAsync: {_asyncLocal.Value}); } private static async Task FooAsync() { await Task.Delay(100); Console.WriteLine($AsyncLocal after await in FooAsync: {_asyncLocal.Value}); } }输出结果AsyncLocal in task: Hello World! AsyncLocal after await in FooAsync: Hello World! AsyncLocal after await FooAsync: Hello World!AsyncLocal 实现原理在我之前的博客 揭秘 .NET 中的 AsyncLocal 中深入介绍了 AsyncLocal 的实现原理这里只做简单的回顾。AsyncLocal 的实际数据存储在 ExecutionContext 中而 ExecutionContext 作为线程的私有字段与线程绑定在线程会发生切换的地方runtime 会将切换前的 ExecutionContext 保存起来切换后再恢复到新线程上。这个保存和恢复的过程是由 runtime 自动完成的例如会发生在以下几个地方new Thread(ThreadStart start).Start()Task.Run(Action action)ThreadPool.QueueUserWorkItem(WaitCallback callBack)await 之后以 await 为例当我们在一个方法中使用了 await 关键词编译器会将这个方法编译成一个状态机这个状态机会在 await 之前和之后分别保存和恢复 ExecutionContext。class Program { private static AsyncLocalstring _asyncLocal new AsyncLocalstring(); static async Task Main(string[] args) { _asyncLocal.Value Hello World!; await FooAsync(); Console.WriteLine($AsyncLocal after await FooAsync: {_asyncLocal.Value}); } private static async Task FooAsync() { await Task.Delay(100); } }输出结果AsyncLocal after await FooAsync: Hello World!AsyncLocal 的坑有时候我们会在 FooAsync 方法中去修改 AsyncLocal 的值并希望在 Main 方法在 await FooAsync 之后能够获取到修改后的值但是实际上这是不可能的。class Program { private static AsyncLocalstring _asyncLocal new AsyncLocalstring(); static async Task Main(string[] args) { _asyncLocal.Value A; Console.WriteLine($AsyncLocal before FooAsync: {_asyncLocal.Value}); await FooAsync(); Console.WriteLine($AsyncLocal after await FooAsync: {_asyncLocal.Value}); } private static async Task FooAsync() { _asyncLocal.Value B; Console.WriteLine($AsyncLocal before await in FooAsync: {_asyncLocal.Value}); await Task.Delay(100); Console.WriteLine($AsyncLocal after await in FooAsync: {_asyncLocal.Value}); } }输出结果AsyncLocal before FooAsync: A AsyncLocal before await in FooAsync: B AsyncLocal after await in FooAsync: B AsyncLocal after await FooAsync: A为什么我们在 FooAsync 方法中修改了 AsyncLocal 的值但是在 await FooAsync 之后AsyncLocal 的值却没有被修改呢原因是 ExecutionContext 被设计成了一个不可变的对象当我们在 FooAsync 方法中修改了 AsyncLocal 的值实际上是创建了一个新的 ExecutionContext原来其他的 AsyncLocal 的值被值拷贝到了新的 ExecutionContext 中新的 AsyncLocal 的值只会写入到新的 ExecutionContext 中而原来的 ExecutionContext 及其关联的 AsyncLocal 仍然保持不变。这样的设计是为了保证线程的安全性因为在多线程环境下如果 ExecutionContext 是可变的那么在切换线程的时候可能会出现数据不一致的情况。我们通常把这种设计称为 Copy On Write简称COW即在修改数据的时候会先拷贝一份数据然后在拷贝的数据上进行修改这样就不会影响到原来的数据。ExecutionContext 中可能不止一个 AsyncLocal 的数据修改任意一个 AsyncLocal 都会导致 ExecutionContext 的 COW。所以上面代码的执行过程如下

相关新闻