Blau
Blau

Reputation: 341

Understanding using multiple backgroundworkers

For example purposes, I have a listview that has 3 items in it. Each item contains a column for the path of a folder and a column for the count of the number of files in that folder.

If I start a separate backgroundworker to count the files in each of the folders, I get an unexpected result. I think I have tracked the problem down, but I'm not sure how to fix it.

In my example below, I've counted the files in each of the folders using two different methods; the first method creates a backgroundworker for each folder with each backgroundworker running concurrently while counting the files. The second method creates one background worker that counts the files in each folder in series. Counting in series does work, while counting concurrently doesn't.

The problem seems to be in the method GetPicturesConcurrently(), specifically on the line that reads:

fileCounter.DoWork += new DoWorkEventHandler((obj, e) => CountFilesInFolder(item.Text)); 

What seems to be happening is that the string that actually gets passed into each call of CountFilesInFolder(string) ends up arriving in the method using the string from the last backgroundworker created; as if the string from item.Text was being passed by reference instead of by value. So I end up counting the files in the same folder over and over again.

When I Break at the time of the backgroundworker creation, I can see that the correct string gets passed each time; and when I Break on CountFilesInFolder, the last string entered gets processed for every call.

Here is an example that demonstrates the problem:

public partial class Form1 : Form
{
    private ConcurrentDictionary<string, int> MyFiles;
    private List<string> Folders;

    public Form1()
    {
        MyFiles = new ConcurrentDictionary<string,int>();
        Folders = new List<string>();

        InitializeComponent();
        PopulateListview();
    }

    private void PopulateListview()
    {
        ListViewItem item1 = new ListViewItem();
        ListViewItem item2 = new ListViewItem();
        ListViewItem item3 = new ListViewItem();

        item1.Text = @"V:\";
        item2.Text = @"D:\";
        item3.Text = @"C:\";

        item1.SubItems.Add("");
        item2.SubItems.Add("");
        item3.SubItems.Add("");

        listView1.Items.Add(item1);
        listView1.Items.Add(item2);
        listView1.Items.Add(item3);
    }



    private void GetPicturesInSeries()
    {
        Reset();

        foreach (ListViewItem item in listView1.Items)
        {
            Folders.Add(item.Text);
        }

        BackgroundWorker fileCounter = new BackgroundWorker();
        fileCounter.DoWork += new DoWorkEventHandler((obj, e) => GetPictures());
        fileCounter.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountListView());
        fileCounter.RunWorkerAsync();            
    }        

    private void GetPicturesConcurrently()
    {
        Reset();


        foreach (ListViewItem item in listView1.Items)
        {
            BackgroundWorker fileCounter = new BackgroundWorker();
            fileCounter.DoWork += new DoWorkEventHandler((obj, e) => CountFilesInFolder(item.Text));
            fileCounter.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountListView(item.Index));
            fileCounter.RunWorkerAsync();               
        }

    }

    private void GetPictures()
    {
        foreach (string folder in Folders)
        {
            CountFilesInFolder(folder);
        }
    }

    private void CountFilesInFolder(string folder)
    {
        DirectoryInfo dirInfo = new DirectoryInfo(folder);

        IEnumerable<FileInfo> files = dirInfo.EnumerateFiles();

        int count = files.Count();

        MyFiles.AddOrUpdate(folder, count, (key, oldvalue) => files.Count());

    }

    private void UpdateCountListView(int index)
    {
        string key = listView1.Items[index].Text;

        int count;
        MyFiles.TryGetValue(key,out count);

        listView1.BeginUpdate();
        listView1.Items[index].SubItems[1].Text = count.ToString();
        listView1.EndUpdate();
        listView1.Refresh();
    }

    private void UpdateCountListView()
    {
        listView1.BeginUpdate();

        foreach (ListViewItem item in listView1.Items)
        {
            string key = item.Text;

            int count;
            MyFiles.TryGetValue(key, out count);

            listView1.Items[item.Index].SubItems[1].Text = count.ToString();
        }

        listView1.EndUpdate();
        listView1.Refresh();
    }

    private void Reset()
    {
        listView1.BeginUpdate();
        foreach (ListViewItem item in listView1.Items)
        {
            item.SubItems[1].Text = "";
        }
        listView1.EndUpdate();
        listView1.Refresh();


        Folders.Clear();
        MyFiles.Clear();
    }
}

Upvotes: 1

Views: 711

Answers (1)

Matthew Watson
Matthew Watson

Reputation: 109862

I think that you're likely to be modifying a captured variable in GetPicturesConcurrently() so change it to make a copy of the variable before using it, like this:

private void GetPicturesConcurrently()
{
    Reset();

    foreach (ListViewItem item in listView1.Items)
    {
        var copy = item;
        BackgroundWorker fileCounter = new BackgroundWorker();
        fileCounter.DoWork += new DoWorkEventHandler((obj, e) => CountFilesInFolder(copy.Text));
        fileCounter.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountListView(copy.Index));
        fileCounter.RunWorkerAsync();               
    }
}

Secondly, your CountFilesInFolder() could possibly be enumerating all the files twice:

private void CountFilesInFolder(string folder)
{
    DirectoryInfo dirInfo = new DirectoryInfo(folder);

    IEnumerable<FileInfo> files = dirInfo.EnumerateFiles();

    int count = files.Count();

    MyFiles.AddOrUpdate(folder, count, (key, oldvalue) => files.Count());
}

If folder is already in MyFiles when you call AddOrUpdate then it will call files.Count() again - which will enumerate all the files again!

If it is not possible for folder to already be in MyFiles then just call MyFiles.Add() instead of MyFiles.AddOrUpdate()

If it is possible for folder to already be in MyFiles then change it to:

MyFiles.AddOrUpdate(folder, count, (key, oldvalue) => count);

Upvotes: 1

Related Questions