BackgroundWorker冻结了我的UI,并且BeginInvoke似乎变得龟速

时间:2014-05-18 05:43:37

标签: c# multithreading winforms backgroundworker

我必须做一个非常简单的任务:1)列出目录中的所有文件(及其子目录),2)在多行文本框中显示它们然后3)在每个文件中做一些事情。我因为2个问题而陷入困境,这就是我所拥有的:

  1. Form1.cs是我管理用户界面并启动BackgroundWorker的地方 运行Logic.cs的主要功能
  2. DependencyMapper.cs是......好吧,我在那里处理文件夹/文件(在Fetch()中)并调用填充每一行的Form1方法(当前文件'的名称)使用Form1's进入BeginInvoke文本框。
  3. 少说话和更多代码。这是我的代码的瘦弱,非常有用的版本:

    Form1.cs

    public partial class Form1 : Form
    {
        public DependencyMapper dep;
        BackgroundWorker bwDep;
    
        public Form1()
        {
            // I read here in SO to try put the BW stuff here don't know why, but hasn't helped.
            InitializeComponent();
    
            bwDep = new BackgroundWorker();
            bwDep.DoWork += bwDep_DoWork;
            bwDep.RunWorkerCompleted += bwDep_RunWorkerCompleted;
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
                bwDep.RunWorkerAsync();
        }
        void bwDep_DoWork(object sender, DoWorkEventArgs e)
        {
            dep.Fetch(extensions);
        }
        public void SendBack(string msg) // To receive Fetch()s progress
        {
            textBox2.BeginInvoke(new Action(() =>
            {
                textBox2.Text += msg + "\r\n";
                textBox2.SelectionStart = textBox2.Text.Length;
                textBox2.ScrollToCaret();
            }));
        }
    }
    

    DependencyMapper.cs

    public class DependencyMapper
    {
        private Form1 form;
        public DependencyMapper(Form1 form1)
        {
            this.form = form1;
        }
        public void Fetch()
        {
            DirectoryInfo folder = new DirectoryInfo(form.Texto1);
            FileInfo[] files = folder.GetFiles("*.*", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                form.SendBack(files[i].FullName); // Kind of talking back to the UI through form's reference and SendBack method which uses BeginInvoke.
            }
        }
    }
    

    那么,我的应用程序是否有效?是的,但是我无法解决的两个巨大问题:

    1. 它冻结了用户界面(wtf lazy BackgroundWorker?)。不完全是因为文本框是逐个添加每个文件但是它应该是这样但我不能移动窗口或单击任何按钮。
    2. 它的速度很慢。我肯定做错了什么。我的应用程序目前以每秒10个文件的速度填充文本框。我正在对其进行编码,以便在数百个文件中找到特定的文本片段... omg
    3. PS:在使用BackgroundWorker之前我使用Thread:用户界面根本没有冻结,但文本框填充率一样慢。这就是为什么我决定冒险BackgroundWorker只能带来问题#1。

      感谢。

1 个答案:

答案 0 :(得分:3)

你是 fire-hosing UI线程,产生的结果远远快于UI可以显示的结果。它可能开始相当不错,但随着时间的推移变得越来越糟。您强制TextBox重新分配内存以查找附加字符串的空间,复制原始文本并附加新文本。一旦文本框包含一兆字节的文本,给予或接受,这将严重开始拖动。与System.String类相同的问题。其中有StringBuilder作为修复,TextBox没有类似的东西。

通常的症状是UI线程将开始刻录100%核心。在某些关键点,它完全是紧张性的,不再重新绘制UI并且对输入没有反应。因为每次更新Text属性时,它都有另一个要调用的委托。没有时间去做低优先级的工作,比如绘制和清空消息队列。调用队列本身开始落后,收集越来越多的调用等待处理的请求。在极端情况下,使用OutOfMemoryException会使程序崩溃,但这需要很长时间。即使后台工作程序完成,UI线程也会忙了很长时间,试图解决积压问题。

UI通常无法正常运行,甚至在它碰壁之前。用户只是盯着一个快速滚动的文本框,它不允许实际读取任何内容。使用文本框本身非常适得其反,用户编辑列表没有任何意义。

显然,你需要以不同的方式做到这一点。在调用之前,至少要在StringBuilder中收集大量文件。这将使TextBox中断,必须经常重新分配。每50毫秒更频繁地调用一次是没有意义的,这与人眼可以看到的变化一样快,而不会变成模糊。大多数执行此操作的程序只显示迭代的文件的示例,以便用户对进度有一些反馈。在这种情况下,在StringBuilder中收集 all 数据并且在文件迭代完成之前不更新TextBox是完全合理的。