0

Here is some code that I use to download a file and then calculate the time remaining time and kbps. It will then post those results on the form by updating a text box and it has a progress bar. The problem I am having is the UI is freezing and I am thinking it might be how I am using the stopwatch but not sure. Anyone have any input?

    /// Downloads the file.
    private void Download_Begin()
    {
        web_client = new System.Net.WebClient();
        web_client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Download_Progress);
        web_client.DownloadFileCompleted += new AsyncCompletedEventHandler(Download_Complete);
        stop_watch = new System.Diagnostics.Stopwatch();
        stop_watch.Start();
        try
        {
            if (Program.Current_Download == "Install_Client.exe")
            {
                web_client.DownloadFileAsync(new Uri("http://www.website.com/Client/Install_Client.exe"), @"C:\Downloads\Install_Client.exe");
            }
            else
            {
                web_client.DownloadFileAsync(new Uri((string.Format("http://www.website.com/{0}", Program.Current_Download))), (string.Format(@"C:\Downloads\{0}", Program.Current_Download)));
            }
        }
        catch(Exception)
        {
            stop_watch.Stop();
        }

        Program.Downloading = true;
        Download_Success = false;
    }
    /// -------------------

    /// Tracks download progress.
    private void Download_Progress(object sender, DownloadProgressChangedEventArgs e)
    {
        double bs = e.BytesReceived / stop_watch.Elapsed.TotalSeconds;

        this.label_rate.Text = string.Format("{0} kb/s", (bs / 1024d).ToString("0.00"));

        long bytes_remaining = e.TotalBytesToReceive - e.BytesReceived;
        double time_remaining_in_seconds = bytes_remaining / bs;
        var remaining_time = TimeSpan.FromSeconds(time_remaining_in_seconds);

        string hours = remaining_time.Hours.ToString("00");

        if (remaining_time.Hours > 99)
        {
            hours = remaining_time.Hours.ToString("000");
        }

        this.time_remaining.Text = string.Format("{0}::{1}::{2} Remaining", hours, remaining_time.Minutes.ToString("00"), remaining_time.Seconds.ToString("00"));

        progressBar1.Maximum = (int)e.TotalBytesToReceive / 100;
        progressBar1.Value = (int)e.BytesReceived / 100;
        if (e.ProgressPercentage == 100)
        {
            Download_Success = true;
        }
    }
    /// -------------------------
David Bentley
  • 824
  • 1
  • 8
  • 27
  • I don't see any code that should cause a freeze. Use a profiler on your program, pretty much every one out there has a feature to filter to UI freezing code. – Scott Chamberlain Oct 12 '16 at 14:33
  • @ScottChamberlain Ok, it seems the actual downloading of the file, the async download has a very HIGH cpu usage. Not sure why it would be doing that. – David Bentley Oct 12 '16 at 15:07
  • David, please refer to this previous question. http://stackoverflow.com/questions/27261797/webclient-downloadprogresschanged-console-writeline-is-blocking-ui-thread It seems that the call back for the download progress gets called quite often. And since you are doing some UI updating in that callback it could potentially cause your UI to freeze. The proposed answer seems like a solution that would work in your case as well. – Wassim H Oct 12 '16 at 15:32

2 Answers2

3

The UI thread could be freezing for various reasons, despite the fact that you are calling the asynchronous download function. One way of preventing the UI to freeze would be to invoke the downloading of the file from different thread than the UI. For instance you can accomplish this with BackgroundWorker and safely modify form's controls via thread safe calls or in short wrapping the code executed in the non-UI thread with BeginInvoke() calls.

private void button1_Click(object sender, EventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += Worker_DoWork;
    worker.RunWorkerAsync();
}

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    Download_Begin();
}

/// Downloads the file.
private void Download_Begin()
{
    web_client = new System.Net.WebClient();
    web_client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Download_Progress);
    web_client.DownloadFileCompleted += new AsyncCompletedEventHandler(Download_Complete);
    stop_watch = new System.Diagnostics.Stopwatch();
    stop_watch.Start();
    try
    {
        if (Program.Current_Download == "Install_Client.exe")
        {
            web_client.DownloadFileAsync(new Uri("http://www.website.com/Client/Install_Client.exe"), @"C:\Downloads\Install_Client.exe");
        }
        else
        {
            web_client.DownloadFileAsync(new Uri((string.Format("http://www.website.com/{0}", Program.Current_Download))), (string.Format(@"C:\Downloads\{0}", Program.Current_Download)));
        }
    }
    catch (Exception)
    {
        stop_watch.Stop();
    }

    Program.Downloading = true;
    Download_Success = false;
}
/// -------------------

/// Tracks download progress.
private void Download_Progress(object sender, DownloadProgressChangedEventArgs e)
{
    this.BeginInvoke(new Action(() => 
    {
        double bs = e.BytesReceived / stop_watch.Elapsed.TotalSeconds;

        this.label_rate.Text = string.Format("{0} kb/s", (bs / 1024d).ToString("0.00"));

        long bytes_remaining = e.TotalBytesToReceive - e.BytesReceived;
        double time_remaining_in_seconds = bytes_remaining / bs;
        var remaining_time = TimeSpan.FromSeconds(time_remaining_in_seconds);

        string hours = remaining_time.Hours.ToString("00");

        if (remaining_time.Hours > 99)
        {
            hours = remaining_time.Hours.ToString("000");
        }

        this.time_remaining.Text = string.Format("{0}::{1}::{2} Remaining", hours, remaining_time.Minutes.ToString("00"), remaining_time.Seconds.ToString("00"));

        progressBar1.Maximum = (int)e.TotalBytesToReceive / 100;
        progressBar1.Value = (int)e.BytesReceived / 100;
        if (e.ProgressPercentage == 100)
        {
            Download_Success = true;
        }
    }));
}
ivayle
  • 1,070
  • 10
  • 17
1

Some thoughts about your code:

  • No need to close stopwatch when exceptions happen, stopwatch does not do any work inside, it simply remember current time when you start stopwatch and calculate difference when you access elapsed time.
  • When you catch all exceptions, there is no need to provide Exception class (i.e. catch instead of catch(Exception)).
  • Mark download as completed only in DownloadFileCompleted event, not in DownloadProgressChanged, because ProgressPercentage can be 100 even when download is not completed yet.
  • When working with async code it is always better to initialize status variables (in your case Download_Success and Program.Downloading) before calling async method, not after.

Now about freezes. DownloadProgreesChanged can be fired very often by WebClient, so UI thread can be flooded by update messages. You need to split report progress and update UI code. UI should be updated in timed manner, for example, twice per second. Very rough code sample below:

    // Put timer on your form, equivalent to:
    // Update_Timer = new System.Windows.Forms.Timer();
    // Update_Timer.Interval = 500;
    // Update_Timer.Tick += Timer_Tick;

    private void Download_Begin()
    {
        web_client = new System.Net.WebClient();
        web_client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(Download_Progress);
        web_client.DownloadFileCompleted += new AsyncCompletedEventHandler(Download_Complete);

        Program.Downloading = true;
        Download_Success = false;

        stop_watch = System.Diagnostics.Stopwatch.StartNew();
        Update_Timer.Start();

        web_client.DownloadFileAsync(new Uri("uri"), "path");
    }

    private int _Progress;

    private void Download_Progress(object sender, DownloadProgressChangedEventArgs e)
    {
        _Progress = e.ProgressPercentage;
    }

    private void Download_Complete(object sender, AsyncCompletedEventArgs e)
    {
        Update_Timer.Stop();
        Program.Downloading = false;
        Download_Success = true;
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        // Your code to update remaining time and speed
        // this.label_rate.Text = ...
        // this.time_remaining.Text = ...
        progressBar1.Value = _Progress;
    }
arbiter
  • 9,447
  • 1
  • 32
  • 43
  • Thanks, I actually already made that change about when my download completed. I am using a system with a counter, but this looks like it would work very well also. Thanks! – David Bentley Oct 14 '16 at 15:22