2011-01-24 6 views
4

をスローそれはループ条件はそれだけでthreadDataためにはIndexOutOfBoundsExceptionが発生する例外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(); 
} 

を起こさないことを非常に明白でありますので、それは非常に奇妙だ[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

個人的には、可能であればforeachを使用するか、LINQを使用することを個人的に考えていきます。List<T>foreachはこの問題を解決しませんでしたが、それは一般的にはより洗練されたIMOです。

List<Thread> threads = threadData.Select(x => new Thread(() => ThreadWork(x))) 
           .ToList(); 
foreach (Thread thread in threads) 
{ 
    thread.Start(); 
} 

もしくは直foreachループで、あなたが行くように、各スレッドを開始:ここで

は、あなたがLINQでそれを行っているかもしれない方法の例です

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

+0

@デッドロック:実際にあなたはしなかった...私はchibacityがした前に少しだけ答えたが、心配しないでください:) –

+0

@ジョン:はい、私は間違っていました。正解はあなたのためにJonに行く。 – deadlock

10

あなたが上でキャプチャしていますループ変数iによって、各スレッドが最終的に実行してthreadDataからデータを取得するときに 'i'の最後の値が使用される可能性があります。例えば、ループ内の変数にiを割り当て、代わりにそれを使用:するために

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

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

エリックリペットはここ現象に非常に良い記事がありますこれがなぜ起こるのかを知るには、ループが終了した後に、将来スレッドが未定義のポイントで実行されると考えてください。 Startは、スレッドを開始する必要があることを通知しますが、実際には非同期的に開始します。これを受けて、Threadに渡されたラムダは、ループが終了しても十分に実行されることがあります。だから、それはどのようにしてiへの参照を持つことができますか?

つまり、コンパイラはiをカプセル化し、iへの参照をこのヘルパークラスに置き換えるヘルパークラスを作成します。これにより、ラムダはループの範囲外で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(); 
     } 
    } 

我々はVarIiの代わりに使用されていることがわかります。自明でない副作用は、スレッドが実行されるときにすべてが共有値、すなわちVarIを参照することである。ループの終了後にスレッドが開始されると、最大値はすべてiになります。

最初のコード例で説明したように、ループ内の一時変数にiを割り当てます。

関連する問題