这很奇怪,因为很明显循环条件永远不会导致异常
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();
}
Run Code Online (Sandbox Code Playgroud)
它只会导致threadData [i]的IndexOutOfBoundsException
Tim*_*oyd 10
您已捕获循环变量i
,这可能导致在每个线程最终执行并从中检索数据时使用"i"的最后一个值threadData
.分配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();
}
Run Code Online (Sandbox Code Playgroud)
Eric Lippert在这里有一篇关于这种现象的非常好的文章:
http://blogs.msdn.com/b/ericlippert/archive/2009/11/16/closing-over-the-loop-variable-part-two.aspx
为了深入了解这种情况发生的原因,请考虑将来可能在循环结束后,线程将在某个未确定的点执行.Start
线程应该启动的信号,它实际上是异步启动,即不是立即启动.鉴于此,我们可以看到传递给lambda的lambda Thread
可以在循环结束后执行得很好.那怎么能引用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();
}
}
Run Code Online (Sandbox Code Playgroud)
在这里我们可以看到VarI
用来代替i
.不明显的副作用是,当线程执行时,它们都会看到共享值,即VarI
.如果线程在循环结束后开始,它们都会看到最大值i
.
修复是分配i
给循环内的临时变量,如第一个代码示例中所述.
这是循环捕获的常见问题 - 您已捕获循环变量,因此在线程实际启动时,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();
}
Run Code Online (Sandbox Code Playgroud)
您可以在Eric Lippert的博客上阅读更多相关信息:第1部分 ; 第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();
}
Run Code Online (Sandbox Code Playgroud)
或者使用直接的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);
}
Run Code Online (Sandbox Code Playgroud)