读取文件的进度条 - 意外 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 友好的解决方案。

A​​aron Thomas 在他的回复中的第一选择提供了实施的总体思路,因此这是公认的答案。

再次感谢您的建议!