理解使用多个后台工作者

本文关键字:后台 工作者 | 更新日期: 2023-09-27 18:05:19

例如,我有一个列表视图,其中有3个项目。每一项包含一列文件夹的路径和一列文件夹中的文件数。

如果我启动一个单独的后台工作程序来计算每个文件夹中的文件,我会得到一个意想不到的结果。我想我已经找到了问题所在,但我不确定如何解决。

在下面的例子中,我使用两种不同的方法计算了每个文件夹中的文件;第一个方法为每个文件夹创建一个backgroundworker,每个backgroundworker在计算文件时并发运行。第二种方法创建一个后台工作程序,它对每个文件夹中的文件进行串行计数。串行计数可以工作,而并发计数则不行。

问题似乎是在方法getpictures(),特别是在这一行,读:

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

似乎正在发生的是,实际传递到CountFilesInFolder(string)的每次调用的字符串最终到达使用从最后一个后台工作器创建的字符串的方法;就像item的字符串一样。文本是通过引用而不是通过值传递的。因此,我不得不一遍又一遍地数同一文件夹中的文件。

当我在创建backgroundworker时Break,我可以看到每次传递的都是正确的字符串;当我在CountFilesInFolder上键入Break时,每次调用都会处理最后输入的字符串。

下面是演示这个问题的示例:

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

理解使用多个后台工作者

我认为你可能会修改GetPicturesConcurrently()中捕获的变量,所以在使用它之前更改它以复制变量,如下所示:

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

其次,您的CountFilesInFolder()可能会枚举所有文件两次:

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

如果folder已经在MyFiles时,您调用AddOrUpdate,那么它将再次调用files.Count() -这将再次枚举所有的文件!

如果folder不可能已经在MyFiles中,那么只需调用MyFiles.Add()而不是MyFiles.AddOrUpdate()

如果folder已经在MyFiles可能的,则将其更改为:

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