[英]BackgroundWorker still freezes UI
我有以下代码,如您所见,后台工作人员正在搜索文件,并且在进度更改中将事件文件添加到列表视图中,但是由于有很多文件添加到列表视图中,因此UI变得无响应,可以让线程在循环中休眠,但是我认为这不是一个好习惯,防止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());
}
因此,这里的问题是ReportProgress
实际上是异步的。 在继续进行工作之前,它不会等待相应的UI更新真正完成。 通常这很好。 在大多数情况下,没有令人信服的理由只是为了等待UI更新而降低生产效率。
但是有一个例外。 如果您如此频繁地调用ReportProgress
以至于在添加下一个进度更新之前实际上没有时间来完成上一个进度更新,那么最终的结果是您在消息队列中填充了进行更新进度的请求。 您有很多文件,并且获取这些文件列表只需要很少的时间。 实际上,与编组UI线程和更新UI相比,它花费的时间要少得多。
由于最终会备份该队列,因此任何其他UI更新都需要经过较长的队列才能执行任何操作。
一种可能的解决方案是分批更新并较少地指示进度 。 鉴于您的情况,它可能会接受也可能不会接受。 几乎可以肯定会有所帮助,但是取决于您根据正在执行的操作更新UI所需的时间以及生成数据的速度,甚至可能会导致问题。 如果它适合您的特定情况,那就太好了。
另一个选项是更改更新进度的方式,以使工作人员在继续之前等待UI更新。 显然,除非需要这样做,否则您应该避免这种情况,因为这意味着虽然您在工作时不冻结UI,但是您的工作将花费更长的时间。 尽管有许多方法可以做到这一点,但最简单的方法可能只是使用Invoke
( 而不是 BeginInvoke
):
foreach (var file in files)
listView1.Invoke(new Action(()=>listView1.Items.Add(file));
虽然从BackgroundWorker
调用Invoke
通常是代码异味,应该避免,但这是一种例外情况。
请注意,即使你最终诉诸使用Invoke
这里我还是建议拌和所调用,这样你添加超过每调用只有一个项目的更多。 如果单个目录中的文件数量足够少,则将整个foreach
放入Invoke
,并且如果您的子目录往往只有很少的文件(即,它们很深,不是很宽),请考虑甚至将所有文件放入进入临时列表,直到它足够大以至于值得将其批量添加到Invoke
。 根据您的数据尝试不同的方法,以了解最有效的方法。
bg.ReportProgress()
旨在将BackgroundWorker的总体进度报告回UI线程,因此您可以将进度告知用户。 但是,您正在使用它实际将字符串添加到ListView。 最好将文件列表编译为内存列表,然后在后台工作程序完成时填充一次listView1:
public void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
foreach (var file in MyFileListVar){
listView1.Items.Add(file);
}
}
尝试加载多个文件(比如说10到50之间),然后将它们发送回UI线程(即bg.ReportProgress
),而不是分别发送每个文件。
您不仅应该使用RunWorkerCompleted事件处理程序将项目添加到ListView,还应该一次调用AddRange而不是多次调用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.