Jake Gearhart
Jake Gearhart

Reputation: 307

Index out of range error when using a list as a parameter for thread start routine

I am writing a C# program that requires giving a thread parameters to a function so that the function will run properly on the separate thread. Specifically one of the parameters is a string name to a file that it is supposed to access. The problem is that I am storing the names of the files in a list and I am accessing the value from the list. However, when I do this I get an index out of range error after one or two threads are created. I think that this is list of strings is my issue, but I know that the index is not out of range.

I am not sure if I am doing something wrong with the way I am passing in the parameters or what else could be wrong.

Here is a sample of my C# code (excluding the code for the functions called):

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++;
            }

Upvotes: 2

Views: 2276

Answers (3)

Brian Gideon
Brian Gideon

Reputation: 48959

The most obvious problem is that you are closing over the loop variable. When you construct a lambda expression any variable references are to the variable itself and not its value. Consider the following code taken from your example.

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

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

  // Code omitted for brevity.
}

What this is actually doing is capturing the variable i. But, by the time the thread starts executing the i could have been incremented several times possibly (and even likely) to the point where its value is now 5. It is possible that the IndexOutOfRangeException is being thrown because surfaceGraphDataNames does not have 6 slots. Nevermind the fact that your thread is not using the value of i that you thought it was.

To fix this you need to create a special capturing variable.

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

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

  // Code omitted for brevity.
}

Upvotes: 0

King King
King King

Reputation: 63367

Looks like that you have to do something like this:

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

The reason is that when you use the variable i, it's not safe because when your i++, and the surfaceGraphDataNames has not been added with new item yet, the exception will throw because your Thread run nearly simultaneously.

Here is the context which leads to the exception:

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));
   //....
}

UPDATE

Looks like that the code above even can't work possibly because the i is increased before the actual ThreadStart is called. I think you can do this to make it safer:

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

Synchronization Attempt:

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

Upvotes: 1

Artem Tsetkhalin
Artem Tsetkhalin

Reputation: 249

I had a problem like this then I use Thread. I was sure that the index is not out of range and this situation not happened if I tried to stop by break-point and then continued. Try to use Task instead of Thread. It works

Upvotes: 0

Related Questions