Search code examples
wpfprogress-barbackgroundworker

Progress Bar for reading a file - unexpected UI behavior


I am trying to update a progress bar while reading a file. The file size will vary between 200Kb up to 50Mb.

I am using System.ComponentModel.BackgroundWorker for the reading process, with these definitions:

progressBar.Minimum = 0

progressBar.Maximum = System.IO.FileInfo.Length (I don't care about percentages).

The reading process:

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);
                }
    }

And the update process:

void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar.Value = e.ProgressPercentage;

        labelProgress.Content = "reading " + e.ProgressPercentage + " out of " + file_length + " bytes";
    }

The UI's reaction is very strange. For a 300Kb file, the progress bar and label are not even updated. They reach the maximum value immediately. For a 50Mb file, they are updated 4 times before completing in under a second.

So I added 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);
                }

This caused the 300Kb file to take roughly a minute to complete, and the 50Mb file.. you get the point.

When I used System.Threading.Thread.Sleep(1), the 300Kb file got to about half way very fast and actually SLOWED DOWN until completing in roughly 5 seconds. The 50Mb file took a very long time to complete.

Of course I can fiddle around with the Thread.Sleep so it will fire once every 10 lines or so, but the performance will change depending on the file size.

Is there a way to consider the file size so that the process will finish in 2~3 seconds regardless of the file size? I know it's possible since reading a 50Mb file takes less than a second to complete (without Thread.Sleep).

Thanks!

EDIT: CODE AFTER SUGGESTIONS (Couldn't submit it as answer for some reason):

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;
    }

Solution

  • Since you are trying to make the progressbar take a minimum of 3 seconds, regardless of file size, bandwidth, other processes on host or client computer, etc... well, there are really only two choices.

    The first choice is to make the progressbar continue even after the download is complete. There are several options with this (show 100% completed for the remaining time, manipulate so it returns an untrue value after downloading, etc).

    The second choice is to throttle the actual download, as you have already practiced. Again, there are many factors present here that are outside control of your code. So, I would suggest adding some calculations so you know how to throttle.

    To remark more on the second choice: you've already shown a basic way to do this by making throttling a percentage of the time it takes to download. You can build on this by reading file size beforehand, and calculating from that. Another option would be to do a partial download of the file (1000 lines, for example), see how long that takes, and extrapolate out for a guess of how long it will take to download the entire file.

    As a case in point of how difficult this can be - if you've seen a MS operating system copying files and displaying "time remaining," how often has that been correct or consistent, even during file transfer?

    Of course you're not calculating time remaining but showing a progress bar. But I would maintain that you're running into the same fundamental hurdles.