【问题标题】:Progress Bar for reading a file - unexpected UI behavior读取文件的进度条 - 意外的 UI 行为
【发布时间】:2015-06-09 13:58:56
【问题描述】:

我正在尝试在读取文件时更新进度条。 文件大小在 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 的文件需要很长时间才能完成。

当然,我可以摆弄 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;
    }

【问题讨论】:

  • 如果您真的想放慢速度以便用户可以看到一个漂亮的进度条,我建议您将文件加载进度与进度条分开。减慢文件加载速度有什么意义?只需加载文件而不对其进行限制,然后每半秒左右更新一次进度条。
  • 感谢您的回复,这是一个有效的解决方案,因为弄清楚如何准确地限制文件读取将是复杂且耗时的。我很快就会发布我的实现。

标签: wpf progress-bar backgroundworker


【解决方案1】:

由于您试图使进度条最少花费 3 秒,无论文件大小、带宽、主机或客户端计算机上的其他进程等...好吧,实际上只有两种选择。

第一个选择是让进度条在下载完成后继续。有几个选项(显示 100% 完成剩余时间,操作使其在下载后返回不真实的值等)。

第二种选择是限制实际下载,正如您已经练习过的那样。同样,这里存在许多您的代码无法控制的因素。所以,我建议添加一些计算,以便您知道如何节流。

关于第二个选择的更多评论:您已经展示了一种基本方法,即限制下载时间的百分比。您可以通过预先读取文件大小并据此计算来构建它。另一种选择是下载文件的部分内容(例如 1000 行),看看需要多长时间,然后推断下载整个文件需要多长时间。

举例说明这可能有多困难 - 如果您看到 MS 操作系统复制文件并显示“剩余时间”,那么这种情况多久正确或一致,即使在文件传输期间也是如此?

当然,您不是在计算剩余时间,而是显示进度条。但我坚持认为,您也遇到了同样的基本障碍。

【讨论】:

  • 谢谢。我采纳了您的第一个建议,稍作改动,稍后我会发布我的代码。
【解决方案2】:

阅读您的问题后,我注意到的第一件事是FileInfo.Length 属性是long 类型,ProgressBar.Maximum 属性是double 类型,所以你很容易在那里遇到问题。

我注意到的下一件事是你打电话给Thread.Sleep(100);,这是一个糟糕的主意。 Thread.Sleep 方法将阻塞 UI 线程,因此不是暂停执行的好方法。取而代之的是,您应该尝试使用Task.Delay method

接下来,我注意到您对progress_precentage += line.Length + 2 的调用将导致progress_precentage 的总数与您之前设置的Maximum 值不匹配。如果您解决了这些问题,可能会有所帮助。

【讨论】:

【解决方案3】:

感谢大家的回复。

睡了一夜好觉并考虑到 Tony Hinkle 的回应,我决定限制文件读取确实是个坏主意。 所以我所做的是读取文件,然后使用 Thread.Sleep(10) 更新进度条,大约需要。 2 秒完成。

对于 50Mb 的文件,用户只会看到轻微的延迟,而对于较小的文件则没有任何延迟。

这有点作弊,但总体来说是一个快速且 UI 友好的解决方案。

Aaron Thomas 在他的回复中的首选提供了实施的总体思路,因此这是公认的答案。

再次感谢您的建议!

【讨论】:

    猜你喜欢
    • 2016-04-19
    • 2018-01-02
    • 2018-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多