BackgroundWorker仍然冻结UI

时间:2014-03-05 17:35:34

标签: c# user-interface backgroundworker

我有以下代码, 正如您所看到后台工作程序搜索文件并且在进度中已将更改的事件文件添加到列表视图中,但是由于有大量文件被添加到列表视图中,因此UI变得无响应,我可以在循环中睡眠但是我不认为这是一种很好的做法,防止用户界面冻结的最佳方式是什么?

详细说明,listview是表单上的表单控件。

void bg_DoWork(object sender, DoWorkEventArgs e)
{
    Stack<string> dirs = new Stack<string>(20);
    dirs.Push(e.Argument.ToString());
    while (dirs.Count > 0)
    {
        string currentDir = dirs.Pop();
        string[] subDirs;
        try { subDirs = System.IO.Directory.GetDirectories(currentDir); }
        catch (UnauthorizedAccessException) { continue; }
        catch (System.IO.DirectoryNotFoundException) { continue; }

        string[] files = null;
        try { files = System.IO.Directory.GetFiles(currentDir); }

        catch (UnauthorizedAccessException) { continue; }
        catch (System.IO.DirectoryNotFoundException) { continue; }
        foreach (var file in files) { bg.ReportProgress(0, file); }
        foreach (string str in subDirs) { dirs.Push(str); }
    }
}
    void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        listView1.Items.Add(e.UserState.ToString());
    }

4 个答案:

答案 0 :(得分:6)

所以这里的问题是ReportProgress实际上是异步的。在继续继续工作之前,它不会等待相应的UI更新实际生成。通常情况下这很好。在大多数情况下,没有令人信服的理由放慢你的生产性工作只是为了等待UI更新。

但有一个例外。如果您经常调用ReportProgress以至于在添加下一个进度更新之前实际上没有时间来完成上一个进度更新,那么最终发生的事情是您填写消息队列以请求更新进度。你有这么多文件,获取这些文件列表只需要很少的时间。它实际上比编组UI线程和更新UI花费的时间要少得多。

由于此队列最终会被备份,因此任何其他UI更新都需要在可以执行任何操作之前通过该长队列。

Batching up the updates and indicating progress less often is one possible solution。根据您的情况,它可能会也可能不会被接受。它几乎肯定会有所帮助,但取决于根据您正在做的事情以及生成数据的速度来更新UI需要多长时间,即使这样也可能可能会引起问题。如果它适用于您的特定情况,那很好。

另一个选项是更改进度的更新方式,以便您的工作人员在继续之前等待UI更新。显然,除非你需要这样做,否则你应该避免这样做,因为这意味着当你在工作时没有冻结用户界面时,你的工作会花费更长的时间。虽然有很多方法可以做到这一点,但最简单的方法是使用Invoke not BeginInvoke):

foreach (var file in files)
    listView1.Invoke(new Action(()=>listView1.Items.Add(file));

Invoke调用BackgroundWorker通常是代码味道,应该避免,这是一个例外情况。

请注意,即使你最终使用Invoke,我仍然会建议对调用进行批处理,以便每次调用只添加一个项目。如果单个目录中的文件数量足够低,则将整个foreach放在Invoke内,如果您的子目录往往只有很少的文件(即,它们非常深,不宽)考虑将所有文件放入临时列表,直到它足够大,值得批量加入Invoke。根据您的数据使用不同的方法,看看什么效果最好。

答案 1 :(得分:4)

bg.ReportProgress()旨在将BackgroundWorker的整体进度报告回UI线程,以便您可以告知用户进度。但是,您正在使用它来实际向ListView添加字符串。您最好将文件列表编译到内存列表中,然后在后台工作程序完成时填充listView1一次:

public void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   foreach (var file in MyFileListVar){
       listView1.Items.Add(file);
   }
}

答案 2 :(得分:1)

尝试加载多个文件(比如介于10~50之间),然后将它们发送回UI线程(即:bg.ReportProgress),而不是单独发送每个文件。

答案 3 :(得分:0)

您不仅要使用RunWorkerCompleted事件处理程序将项目添加到ListView,还应该多次调用AddRange而不是多次添加。