1. 闭包陷阱现象重现:当foreach遇上异步
第一次遇到这个坑是在三年前的一个电商项目里。当时需要批量处理订单状态更新,我写了类似这样的代码:
csharp复制var tasks = new List<Task>();
foreach (var orderId in orderIds)
{
tasks.Add(Task.Run(() => {
Console.WriteLine($"Processing order {orderId}");
UpdateOrderStatus(orderId);
}));
}
await Task.WhenAll(tasks);
结果控制台输出的orderId全部变成了最后一个订单ID,更新操作也全都作用在同一个订单上。这个bug让我排查了整整一个下午,直到发现是闭包捕获机制在作祟。
1.1 现象背后的本质原因
在C#中,foreach循环的迭代变量(上面的orderId)实际上是被所有闭包共享的同一个变量。当异步操作真正执行时,循环可能已经结束,此时捕获的orderId值自然就是最后一次迭代的值。
这就像在电影院散场时,你告诉朋友"我在出口等你",但所有观众共享同一个出口标识。当大家真的走到出口时,看到的永远是最后被设置的那个位置。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 闭包机制深度解析
2.1 C#编译器的代码转换
编译器会将foreach循环转换为类似这样的结构:
csharp复制IEnumerator<int> enumerator = orderIds.GetEnumerator();
try {
while (enumerator.MoveNext()) {
int orderId = enumerator.Current; // 关键点:每次循环复用同一个变量
tasks.Add(Task.Run(() => {
Console.WriteLine($"Processing order {orderId}");
}));
}
} finally {
enumerator.Dispose();
}
注意orderId是在循环外部
