2014-05-18 110 views
2

我必须做一个非常容易的任务:1)列出目录(及其子目录)中的所有文件,2)在多行文本框中显示它们,然后3)在每个文件中做一些东西。我被困在2)由于2个问题,这是我有:BackgroundWorker冻结我的UI和BeginInvoke似乎去乌龟的速度

  1. Form1.cs就是我管理的UI,并开始一个BackgroundWorker 在运行Logic.cs的主要功能
  2. DependencyMapper.cs是.. 。好吧,我在那里做文件夹/文件(在Fetch()中),并使用BeginInvoke将每行(当前文件名)填充到Form1的文本框中调用Form1函数。

说话少,代码多。这是我的代码的骨感,不亦乐乎工作版本:

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. 它冻结UI(WTF懒BackgroundWorker的?)。不完全是因为文本框逐个添加每个文件,但它像是应该的,但我无法移动窗口或单击任何按钮。
  2. 这是veeery缓慢。肯定我做错了什么。我的应用程序目前以每秒10个文件的速度填充文本框。而我编码它来查找文本的特定片段在数百个文件... OMG

PS:使用的BackgroundWorker之前,我使用线程:用户界面没有冻结,不过在文本框填充比例为一样慢。这就是为什么我决定冒险与BackgroundWorker,只带来了问题#1。

谢谢。

+0

尝试在窗体初始化之前将线程(背景)准备好与文件夹和文件的列表一起准备好。考虑如何通过表格构建或在全球范围内提供的列表。 – ray

+1

'dep.Fetch(extensions);'什么是dep?什么是扩展?取参数的功能在哪里?请张贴确切的代码。如果适用,考虑只将最终的relult'string []'传递给表单。 –

+0

当您需要在后台执行某些操作时,线程非常方便。自从您不断与用户界面交谈之后,您实际上并未从中受益。当您完全在后台完成繁重的操作并在完成后将结果传递给您的用户界面时,才会带来真正的好处。 – Silvermind

回答

3

你是fire-hosing UI线程,产生的结果远远快于UI可以显示它们。它可能起步相当好,但随着时间的推移会变得越来越糟。您强制文本框重新分配内存以找到附加字符串的空间,复制原始文本并追加新文本。当文本框包含一个兆字节的文本时,这会严重拖动。与System.String类具有相同类型的问题。其中有StringBuilder作为修复,TextBox没有任何类似的东西。

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

用户界面通常无法使用,甚至在触及墙壁之前。用户只是盯着一个盲目快速的滚动文本框,它不允许实际读取任何内容。本身使用文本框会非常不利,用户编辑列表没有任何意义。

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

+0

我放弃了更新用户的进度(我仍然编码我想要做的实际逻辑),而是将结果全部打包。但这是一个令人难以置信的回应,给了我一个广阔的视角,谢谢。 –