Yoav
Yoav

Reputation: 3386

Getting wrong results from a Thread

I have a small WPF application that needs to enumerate through all files in a specified directory and check if a certain string exists in it. this is the search method:

private void btnSearch_Click_1(object sender, RoutedEventArgs e)
{
  Thread t = new Thread(()=>search(@"c:\t", "url", true));
  t.Start();
}

private void search(string path, string textToSearch, bool ignoreCase)
{
  foreach (string currentFile in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
  {
    int lineNumber = 0;
    foreach (string line in File.ReadLines(currentFile))
    {
      lineNumber++;
      if (line.Contains(textToSearch))
      {
        lbFiles.Dispatcher.BeginInvoke((Action)(() =>
        {
          //add the file name and the line number to a ListBox
          lbFiles.Items.Add(currentFile + "     " + lineNumber);
        }));
      }
    }
  }
}

My problem is that if the specified string is found more than once in the file, the line number will be the latter for all occurrences. for a text file that has the following lines:

abcd
EFG
url
hijk123
url

the listbox will look like this:

ListBoxResult

when stepping through the code with a breakpoint I can see that immediately after steping out of the search method it "jumps" back into the BeginInvoke declaration.
Please advise.
Thanks

Upvotes: 0

Views: 59

Answers (1)

Servy
Servy

Reputation: 203821

The issue is that you're closing over the variable lineNumber. BeginInvoke is asynchronous, it doesn't wait for the delegate to be called on the UI thread. By the time it manages to get invoked lineNumber has been incremented a number of times.

There are two solutions. Create a more localized copy of lineNumber to close over so that the changes aren't seen later:

foreach (string line in File.ReadLines(currentFile))
{
  lineNumber++;
  if (line.Contains(textToSearch))
  {
    var lineNumberCopy = lineNumber;
    lbFiles.Dispatcher.BeginInvoke((Action)(() =>
    {
      //add the file name and the line number to a ListBox
      lbFiles.Items.Add(currentFile + "     " + lineNumberCopy );
    }));
  }
}

Or use Invoke instead of BeginInvoke, so that lineNumber is read from before it has a chance to be incremented.

Upvotes: 1

Related Questions