【问题标题】:Understanding using multiple backgroundworkers了解使用多个后台工作者
【发布时间】:2013-03-23 17:49:41
【问题描述】:

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

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

在下面的示例中,我使用两种不同的方法计算了每个文件夹中的文件;第一种方法为每个文件夹创建一个后台工作程序,每个后台工作程序在计算文件时同时运行。第二种方法创建一个后台工作人员,该工作人员按顺序计算每个文件夹中的文件。串行计数确实有效,而同时计数则无效。

问题似乎出在 GetPicturesConcurrently() 方法中,特别是在以下行:

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

似乎正在发生的事情是,实际传递给每个 CountFilesInFolder(string) 调用的字符串最终会使用最后一个创建的后台工作人员的字符串到达​​方法中;好像 item.Text 中的字符串是通过引用而不是值传递的。所以我最终一遍又一遍地计算同一个文件夹中的文件。

当我在后台工作程序创建时 Break 时,我可以看到每次都传递了正确的字符串;当我在 CountFilesInFolder 上中断时,每次调用都会处理最后输入的字符串。

这是一个演示问题的示例:

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

【问题讨论】:

    标签: c# backgroundworker


    【解决方案1】:

    我认为您可能正在修改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());
    }
    

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

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

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

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

    【讨论】:

    • 非常感谢!我以前从未听说过捕获的变量,但我现在正在阅读它们。使用捕获的变量进行建议的更改可以解决问题。至于关于两次枚举所有文件的可能性的评论; MyFiles 集合是一个 ConcurrentDictionary,因此没有 Add() 函数。我认为我的选择是 AddOrUpdate() 或 TryAdd()。但是我明白您所说的两次枚举事物的说法,所以我一定会采纳您的宝贵建议并将其更改为 count 而不是 files.count()。再次感谢!
    • 啊,是的,ConcurrentDictionary 隐藏了 Add() 方法,我忘了。
    猜你喜欢
    • 2011-07-18
    • 1970-01-01
    • 2019-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-29
    • 1970-01-01
    相关资源
    最近更新 更多