为何Test()中对AsyncLocal的修改未在Main方法中生效?
问题描述
调用Test()方法时,对data.Value的修改无法在Main方法中体现,即使Test()方法未使用await,理论上应同步执行。
代码示例
public class Data { public Data data; public int count; } public class Program { public static AsyncLocal<Data> data = new(); public static async Task Main(string[] args){ data.Value = new Data{ data = new Data{ count = 5 }, count = 10 }; Console.WriteLine($"[Main] - Data = {data?.Value?.GetHashCode()}, Data Count = {data?.Value?.count}, " + $"SubData Count = {data?.Value?.data?.count},\n ThreadId = {Thread.CurrentThread.ExecutionContext!.GetHashCode()}"); Console.WriteLine($"[Main] - AsyncLocal HashCode = {data.GetHashCode()}"); var task = Test(); Console.WriteLine($"[Main] - Data = {data?.Value?.GetHashCode()}, Data Count = {data?.Value?.count}, " + $"SubData Count = {data?.Value?.data?.count},\n ThreadId = {Thread.CurrentThread.ExecutionContext.GetHashCode()}"); Console.WriteLine($"[Main] - AsyncLocal HashCode = {data.GetHashCode()}"); await task; } private static async Task Test() { data.Value = new Data{ data = new Data{ count = 15 }, count = 20 }; Console.WriteLine($"Data = {data.GetHashCode()}, Data Count = {data?.Value?.count}, SubData Count = {data?.Value?.data?.count}, " + $"\n ThreadId = {Thread.CurrentThread.ExecutionContext.GetHashCode()}"); Console.WriteLine($"AsyncLocal HashCode = {data.GetHashCode()}"); } }
运行输出
[Main] - Data = 58225482, Data Count = 10, SubData Count = 5,
ThreadId = 54267293
[Main] - AsyncLocal HashCode = 18643596
Data = 18643596, Data Count = 20, SubData Count = 15,
ThreadId = 33574638
AsyncLocal HashCode = 18643596
[Main] - Data = 58225482, Data Count = 10, SubData Count = 5,
ThreadId = 54267293
[Main] - AsyncLocal HashCode = 18643596
原因解析
这是AsyncLocal
哪怕Test方法里没有await,只要它是async Task方法,调用Test()时就会触发执行上下文的复制逻辑:
- Main方法始终使用原始执行上下文,其中的
data.Value指向初始创建的Data实例(HashCode=58225482)。 - 进入
Test方法后,系统会基于当前上下文生成新的执行上下文副本,此时修改data.Value是在这个副本中赋值新的Data实例,所有操作都局限在副本内。 - 当
Test执行完成回到Main方法时,Main仍然沿用原始执行上下文,自然看不到Test里的修改。
从输出的ThreadId也能佐证:Test运行在独立线程(33574638),而Main始终在原线程(54267293),AsyncLocal的上下文隔离机制不仅跨线程生效,即使是同线程下的异步方法,执行上下文副本也是完全独立的。
内容的提问来源于stack exchange,提问作者Florent Bunjaku

