CSharp 中 Thread 和 Foreach

🌴前言

最近一个项目中需要用到多线程, 而在研究多线程的过程中发现了 foreach 的一个特性.

❓问题

网上有一种说法, 如下代码会出现一个问题, 导致最后输出的结果为: 五个 555 字符串.

理由和之前说过的 "闭包和委托" 结合容易引起的问题一样, 下面是一个博主的解释:

由于被置于 ThreadPool 中的操作时异步的, 还没有来的及执行的时候, inFor 变量已经被循环改变, 等到 ThreadPool 执行的时候, inFor 已经是最后一个值了, 也就是 555, 所以最终的结果是输出了五次 555.

1
2
3
4
5
6
7
8
9
10
11
12
public static void MultiTest()
{
var list = new List<int> {111, 222, 333, 444, 555};

foreach (var inFor in list)
{
System.Threading.ThreadPool.QueueUserWorkItem(state =>
{
DebugUtil.Log($"<color='red'>{inFor}</color>");
});
}
}

✨结论

而实际经过实验之后发现, 目前 C# 和 Unity 中都不会再出现这种问题, 下面是我实验的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void MultiTest()
{
var list = new List<int> {111, 222, 333, 444, 555};

// 不使用外部变量
foreach (var inFor in list) {
System.Threading.ThreadPool.QueueUserWorkItem(state => {
DebugUtil.Log($"<color='red'>{inFor}</color>");
});
}

Thread.Sleep(10);

// 使用外部变量
int outFor;
foreach (var inFor in list) {
outFor = inFor;
System.Threading.ThreadPool.QueueUserWorkItem(state => {
DebugUtil.Log($"<color='yellow'>{outFor}</color>");
});
}
}

最终得到的结果是:

foreach

可以看到, 当仅使用 foreach 时得到的结果是完全正确的, 只有当使用一个外部变量的时候才会出现闭包机制引发的 Bug, 因此可得出表现上的结论:

foreach 中的临时变量从表现上来看可以认作每次循环后都是一个新的变量.