2011-01-24 49 views
4

这很奇怪,因为这是非常明显的是,循环条件永远不会导致异常C#从一个循环启动线程抛出IndexOutOfBoundsException异常

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(); 
} 

它只是导致IndexOutOfBoundsException异常的threadData [I]

回答

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(); 
} 

你可以阅读更多有关这对埃里克利珀的博客: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); 
} 
+0

感谢乔恩,我为那个先回答的人标记了正确的答案。 – deadlock 2011-01-24 20:54:42

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(); 
} 

埃里克利珀对现象在这里一个非常漂亮的文章:

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

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分配给循环内的临时变量,如第一个代码示例中所述。