使用列表作为线程启动例程的参数时,索引超出范围错误

时间:2013-09-03 03:05:57

标签: c# multithreading lambda delegates indexoutofboundsexception

我正在编写一个C#程序,它需要为函数提供一个线程参数,以便该函数在单独的线程上正常运行。具体来说,其中一个参数是它应该访问的文件的字符串名称。问题是我将文件的名称存储在列表中,我正在从列表中访问该值。但是,当我这样做时,在创建一个或两个线程后,我得到索引超出范围错误。我认为这是字符串列表是我的问题,但我知道索引不在范围之外。

我不确定我在传递参数的方式或其他可能出错的方面是否有问题。

以下是我的C#代码示例(不包括所调用函数的代码):

for (int i = 0; i < 5; i++)
            {
                surfaceGraphDataNames.Add(String.Format(surfacePlotDataLocation+"ThreadData{0}.txt", i));
                try
                {
                    generateInputFile(masterDataLocation);
                }
                catch
                {
                    MessageBox.Show("Not enough data remaining to create an input file");
                    masterDataLocation = masterDataSet.Count - ((graphData.NumRootsUsed + 1) * (graphData.Polynomial + 1) - 1);
                    this.dataSetLabel.Text = String.Format("Current Data Set: {0}", masterDataLocation + 1);
                    return;
                }
                try
                {
                    //creates the data in a specific text file I hope
                    createSurfaceGraph(surfaceGraphDataNames[i]);
                    //start threads 
                    threadsRunning.Add(new Thread(() => runGnuplotClicks(surfaceGraphDataNames[i], masterDataLocation)));
                    threadsRunning[i].Start();
                }
                catch
                {
                    this.graphPictureBox1.Image = null;//makes image go away if data fails
                    MessageBox.Show("Gridgen failed to generate good data");
                }
                masterDataLocation++;
            }

3 个答案:

答案 0 :(得分:1)

看起来你必须做这样的事情:

threadsRunning.Add(new Thread(() => {
           var k = i;
           runGnuplotClicks(surfaceGraphDataNames[k], masterDataLocation)
          }
        ));

原因是当您使用变量i时,它不安全,因为当您的i++surfaceGraphDataNames尚未添加新项目时,异常将抛出因为你的Thread几乎同时运行。

以下是导致异常的上下文:

for(int i = 0; i < 5; i++){
   //Suppose i is increased to 3 at here
   //Here is where your Thread running code which accesses to the surfaceGraphDataNames[i]   
   //That means it's out of range at this time because
   //the surfaceGraphDataNames has not been added with new item by the code below 
   surfaceGraphDataNames.Add(String.Format(surfacePlotDataLocation+"ThreadData{0}.txt", i));
   //....
}

更新

看起来上面的代码甚至无法工作,因为i在调用实际ThreadStart之前增加了。我认为你可以做到这一点让它更安全:

var j = i;
threadsRunning.Add(new Thread(() => {
     var k = j;
     runGnuplotClicks(surfaceGraphDataNames[k], masterDataLocation)
    }
));

同步尝试:

Queue<int> q = new Queue<int>();
for(int i = 0; i < 5; i++){
  //.....
  q.Enqueue(i);
  threadsRunning.Add(new Thread(() => {       
     runGnuplotClicks(surfaceGraphDataNames[q.Dequeue()], masterDataLocation)
    }
  ));
  threadsRunning[i].Start();
}

答案 1 :(得分:0)

我有这样的问题然后我使用Thread。我确信指数并没有超出范围,如果我试图以突破点停止然后继续,这种情况就不会发生。 尝试使用Task而不是Thread。它的工作原理

答案 2 :(得分:0)

最明显的问题是你是closing over the loop variable。构造lambda表达式时,任何变量引用都是变量本身而不是。请考虑以下代码中的代码。

for (int i = 0; i < 5; i++)
{
  // Code omitted for brevity.

  new Thread(() => runGnuplotClicks(surfaceGraphDataNames[i], masterDataLocation))

  // Code omitted for brevity.
}

实际上这是捕获变量i。但是,当线程开始执行时,i可能已经多次(甚至可能)递增到其值现在为5的点。可能会抛出IndexOutOfRangeException因为surfaceGraphDataNames没有6个插槽。没关系,你的线程没有使用你认为的i值。

要解决此问题,您需要创建一个特殊的捕获变量。

for (int i = 0; i < 5; i++)
{
  // Code omitted for brevity.

  int capture = i;
  new Thread(() => runGnuplotClicks(surfaceGraphDataNames[capture], masterDataLocation))

  // Code omitted for brevity.
}