读取文件的进度条 - 意外 UI 行为
Progress Bar for reading a file - unexpected UI behavior
我正在尝试在读取文件时更新进度条。
文件大小在 200Kb 到 50Mb 之间变化。
我在阅读过程中使用 System.ComponentModel.BackgroundWorker,定义如下:
progressBar.Minimum = 0
progressBar.Maximum = System.IO.FileInfo.Length(我不关心百分比)。
阅读过程:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bg = sender as BackgroundWorker;
while (!reader.EndOfStream)
{
line = reader.ReadLine();
file_content.Add(line);
progress_precentage += line.Length + 2;
System.Threading.Thread.Sleep(100);
bg.ReportProgress(progress_precentage);
}
}
以及更新过程:
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
labelProgress.Content = "reading " + e.ProgressPercentage + " out of " + file_length + " bytes";
}
UI的反应很奇怪。
对于一个 300Kb 的文件,进度条和标签甚至都没有更新。它们立即达到最大值。
对于一个 50Mb 的文件,它们会在一秒钟内完成更新 4 次。
所以我添加了System.Threading.Thread.Sleep:
while (!reader.EndOfStream)
{
line = reader.ReadLine();
file_content.Add(line);
progress_precentage += line.Length + 2;
System.Threading.Thread.Sleep(100);
bg.ReportProgress(progress_precentage);
}
这导致 300Kb 的文件大约需要一分钟才能完成,而 50Mb 的文件..你明白了。
当我使用 System.Threading.Thread.Sleep(1) 时,这个 300Kb 的文件非常快地完成了大约一半,实际上放慢了速度,直到大约 5 秒内完成。
50Mb 的文件花了很长时间才完成。
当然我可以 fiddle 使用 Thread.Sleep 这样它每 10 行左右触发一次,但性能会根据文件大小而变化。
有没有办法考虑文件大小,以便无论文件大小如何,该过程都将在 2~3 秒内完成?我知道这是可能的,因为读取一个 50Mb 的文件只需不到一秒的时间即可完成(没有 Thread.Sleep)。
谢谢!
编辑:
建议后的代码(由于某种原因无法将其作为答案提交):
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bg = sender as BackgroundWorker;
try
{
file_content = System.IO.File.ReadAllLines(file_path).ToList();
}
catch ()
{
bg.ReportProgress(-1);
file_read_successful = false;
return;
}
//For i from 0 to 100
System.Threading.Thread.Sleep(10);
bg.ReportProgress(i);
file_read_successful = true;
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
//Failure to read file
if (e.ProgressPercentage < 0)
{
//Show popup with failure message
textBlockFailure.Text = (string)e.UserState;
popupSelect.IsOpen = true;
return;
}
labelProgress.Content = e.ProgressPercentage + "%";
progressBar.Value = e.ProgressPercentage;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (file_read_successful)
{
labelProgress.Content = "Done!";
progressBar.Value = progressBar.Maximum;
}
else
{
labelProgress.Content = "";
progressBar.Value = progressBar.Minimum;
}
//Unregister events
worker.DoWork -= worker_DoWork;
worker.ProgressChanged -= worker_ProgressChanged;
worker.RunWorkerCompleted -= worker_RunWorkerCompleted;
}
因为你试图让进度条花费 最小值 3 秒,无论文件大小、带宽、主机或客户端计算机上的其他进程等等......好吧,真的只有两个选择。
第一个选择是让进度条在下载完成后继续。这有几个选项(在剩余时间内显示 100% 完成,操纵它 returns 下载后的不真实值,等等)。
第二种选择是限制实际下载,正如您已经练习过的那样。同样,此处存在许多超出代码控制范围的因素。所以,我建议添加一些计算,以便您知道如何节流。
关于第二个选择的更多评论:您已经展示了一种基本方法,通过对下载时间的一定百分比进行节流。您可以通过预先读取文件大小并据此进行计算来以此为基础。另一种选择是部分下载文件(例如 1000 行),看看需要多长时间,然后推断下载整个文件需要多长时间。
举个例子说明这有多困难 - 如果您看到 MS 操作系统复制文件并显示 "time remaining," 正确或一致的频率是多少,即使在文件传输期间也是如此?
当然你不是在计算剩余时间,而是在显示进度条。但我会坚持认为您 运行 遇到了相同的基本障碍。
读完你的问题后,我注意到的第一件事是 FileInfo.Length
属性 是 long
类型, ProgressBar.Maximum
属性 是double
类型的,所以你很容易在那里遇到问题。
我注意到的下一件事是您正在调用 Thread.Sleep(100);
,这是一个糟糕的主意。 Thread.Sleep
方法将阻塞 UI 线程,因此不是暂停执行的好方法。相反,您应该尝试使用 Task.Delay
method.
接下来,我注意到您对 progress_precentage += line.Length + 2
的调用将导致总数 progress_precentage
与您之前设置的 Maximum
值不匹配。如果您解决了这些问题,可能会有所帮助。
谢谢大家的回复。
经过一夜安眠并考虑到 Tony Hinkle 的回应,我决定限制文件读取确实是个坏主意。
所以我所做的是读取文件,然后用 Thread.Sleep(10) 更新进度条,大约需要。 2 秒完成。
对于 50Mb 的文件,用户只会看到轻微的延迟,对于较小的文件,none 无论如何。
有点作弊,但总的来说是一个快速且 UI 友好的解决方案。
Aaron Thomas 在他的回复中的第一选择提供了实施的总体思路,因此这是公认的答案。
再次感谢您的建议!
我正在尝试在读取文件时更新进度条。 文件大小在 200Kb 到 50Mb 之间变化。
我在阅读过程中使用 System.ComponentModel.BackgroundWorker,定义如下:
progressBar.Minimum = 0
progressBar.Maximum = System.IO.FileInfo.Length(我不关心百分比)。
阅读过程:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bg = sender as BackgroundWorker;
while (!reader.EndOfStream)
{
line = reader.ReadLine();
file_content.Add(line);
progress_precentage += line.Length + 2;
System.Threading.Thread.Sleep(100);
bg.ReportProgress(progress_precentage);
}
}
以及更新过程:
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
labelProgress.Content = "reading " + e.ProgressPercentage + " out of " + file_length + " bytes";
}
UI的反应很奇怪。 对于一个 300Kb 的文件,进度条和标签甚至都没有更新。它们立即达到最大值。 对于一个 50Mb 的文件,它们会在一秒钟内完成更新 4 次。
所以我添加了System.Threading.Thread.Sleep:
while (!reader.EndOfStream)
{
line = reader.ReadLine();
file_content.Add(line);
progress_precentage += line.Length + 2;
System.Threading.Thread.Sleep(100);
bg.ReportProgress(progress_precentage);
}
这导致 300Kb 的文件大约需要一分钟才能完成,而 50Mb 的文件..你明白了。
当我使用 System.Threading.Thread.Sleep(1) 时,这个 300Kb 的文件非常快地完成了大约一半,实际上放慢了速度,直到大约 5 秒内完成。 50Mb 的文件花了很长时间才完成。
当然我可以 fiddle 使用 Thread.Sleep 这样它每 10 行左右触发一次,但性能会根据文件大小而变化。
有没有办法考虑文件大小,以便无论文件大小如何,该过程都将在 2~3 秒内完成?我知道这是可能的,因为读取一个 50Mb 的文件只需不到一秒的时间即可完成(没有 Thread.Sleep)。
谢谢!
编辑: 建议后的代码(由于某种原因无法将其作为答案提交):
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bg = sender as BackgroundWorker;
try
{
file_content = System.IO.File.ReadAllLines(file_path).ToList();
}
catch ()
{
bg.ReportProgress(-1);
file_read_successful = false;
return;
}
//For i from 0 to 100
System.Threading.Thread.Sleep(10);
bg.ReportProgress(i);
file_read_successful = true;
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
//Failure to read file
if (e.ProgressPercentage < 0)
{
//Show popup with failure message
textBlockFailure.Text = (string)e.UserState;
popupSelect.IsOpen = true;
return;
}
labelProgress.Content = e.ProgressPercentage + "%";
progressBar.Value = e.ProgressPercentage;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (file_read_successful)
{
labelProgress.Content = "Done!";
progressBar.Value = progressBar.Maximum;
}
else
{
labelProgress.Content = "";
progressBar.Value = progressBar.Minimum;
}
//Unregister events
worker.DoWork -= worker_DoWork;
worker.ProgressChanged -= worker_ProgressChanged;
worker.RunWorkerCompleted -= worker_RunWorkerCompleted;
}
因为你试图让进度条花费 最小值 3 秒,无论文件大小、带宽、主机或客户端计算机上的其他进程等等......好吧,真的只有两个选择。
第一个选择是让进度条在下载完成后继续。这有几个选项(在剩余时间内显示 100% 完成,操纵它 returns 下载后的不真实值,等等)。
第二种选择是限制实际下载,正如您已经练习过的那样。同样,此处存在许多超出代码控制范围的因素。所以,我建议添加一些计算,以便您知道如何节流。
关于第二个选择的更多评论:您已经展示了一种基本方法,通过对下载时间的一定百分比进行节流。您可以通过预先读取文件大小并据此进行计算来以此为基础。另一种选择是部分下载文件(例如 1000 行),看看需要多长时间,然后推断下载整个文件需要多长时间。
举个例子说明这有多困难 - 如果您看到 MS 操作系统复制文件并显示 "time remaining," 正确或一致的频率是多少,即使在文件传输期间也是如此?
当然你不是在计算剩余时间,而是在显示进度条。但我会坚持认为您 运行 遇到了相同的基本障碍。
读完你的问题后,我注意到的第一件事是 FileInfo.Length
属性 是 long
类型, ProgressBar.Maximum
属性 是double
类型的,所以你很容易在那里遇到问题。
我注意到的下一件事是您正在调用 Thread.Sleep(100);
,这是一个糟糕的主意。 Thread.Sleep
方法将阻塞 UI 线程,因此不是暂停执行的好方法。相反,您应该尝试使用 Task.Delay
method.
接下来,我注意到您对 progress_precentage += line.Length + 2
的调用将导致总数 progress_precentage
与您之前设置的 Maximum
值不匹配。如果您解决了这些问题,可能会有所帮助。
谢谢大家的回复。
经过一夜安眠并考虑到 Tony Hinkle 的回应,我决定限制文件读取确实是个坏主意。 所以我所做的是读取文件,然后用 Thread.Sleep(10) 更新进度条,大约需要。 2 秒完成。
对于 50Mb 的文件,用户只会看到轻微的延迟,对于较小的文件,none 无论如何。
有点作弊,但总的来说是一个快速且 UI 友好的解决方案。
Aaron Thomas 在他的回复中的第一选择提供了实施的总体思路,因此这是公认的答案。
再次感谢您的建议!