这很奇怪,因为很明显循环条件永远不会导致异常
Thread [] threads = new Thread[threadData.Length];
for (int i = 0; i < threadData.Length; i++)
{
threads[i]= new System.Threading.Thread(() => threadWork(threadData[i]));
threads[i].Start();
}
它只会导致threadData [i]
的IndexOutOfBoundsException答案 0 :(得分:10)
您已捕获循环变量i
,这可能导致在每个线程最终执行并从threadData
检索数据时使用“i”的最后一个值。将i
分配给循环中的变量并使用它,例如:
Thread [] threads = new Thread[threadData.Length];
for (int i = 0; i < threadData.Length; i++)
{
int index = i;
threads[i]= new System.Threading.Thread(() => threadWork(threadData[index]));
threads[i].Start();
}
Eric Lippert在这里有一篇关于这种现象的非常好的文章:
http://blogs.msdn.com/b/ericlippert/archive/2009/11/16/closing-over-the-loop-variable-part-two.aspx
为了深入了解这种情况发生的原因,请考虑将来可能在循环结束后,线程将在某个未确定的点执行。 Start
表示线程应该启动,它实际上是异步启动,即不是立即启动。鉴于此,我们可以看到传递给Thread
的lambda可能在循环结束后执行得很好。那么如何引用i
?
简而言之,编译器将创建一个可以封装i
的辅助类,然后用这个辅助类替换对i
的引用。这允许lambda在循环范围之外引用i
。编译魔术的一个很好的例子,但在这种情况下有一个非明显的副作用,即它捕获循环变量:
private class LambdaHelper
{
public int VarI { get; set; }
}
private static void SomeMethod()
{
LambdaHelper helper = new LambdaHelper();
Thread[] threads = new Thread[threadData.Length];
for (helper.VarI = 0; helper.VarI < data.Length; helper.VarI++)
{
threads[helper.VarI] = new Thread(() => ThreadWork(data[helper.VarI]));
threads[helper.VarI].Start();
}
}
我们可以看到VarI
代替i
。不明显的副作用是当线程执行时它们都看到共享值,即VarI
。如果线程在循环结束后开始,则它们都将看到i
的最大值。
修复是将i
分配给循环内的临时变量,如第一个代码示例中所述。
答案 1 :(得分:8)
这是循环捕获的常见问题 - 您已捕获循环变量,因此在线程实际启动时,i
是最终值,这是数组的无效索引。解决方案是在循环中创建一个新的变量 ,然后捕获它:
Thread[] threads = new Thread[threadData.Length];
for (int i = 0; i < threadData.Length; i++)
{
int copy = i;
threads[i]= new System.Threading.Thread(() => threadWork(threadData[copy]));
threads[i].Start();
}
您可以在Eric Lippert的博客上阅读更多相关信息:part 1; part 2
我个人会考虑更多地使用List<T>
,并尽可能使用foreach
- 甚至LINQ。诚然,foreach
不会解决这个问题,但IMO通常会更清晰。
这是一个如何在LINQ中完成它的示例:
List<Thread> threads = threadData.Select(x => new Thread(() => ThreadWork(x)))
.ToList();
foreach (Thread thread in threads)
{
thread.Start();
}
或者使用直接的foreach循环,随时启动每个线程:
List<Thread> threads = new List<Thread>();
foreach (var data in threadData)
{
var dataCopy = data;
Thread thread = new Thread(() => ThreadWork(dataCopy));
thread.Start();
threads.Add(thread);
}