Why when downloading files the progressbar and the percentages on the labels and the app it self freeze hang for a second or so before "jumping" to the finish of download and displaying the message download successfully ?

Daniel Lee 61 Reputation points
2023-12-11T19:25:00.0766667+00:00

instead showing the progressbar and the percentages smooth from 0 to 100% and all the information on the label for each downloading file it's each some seconds hang for a second or so then for example when getting to 16% or to 65% or any percentage it's hanging then jump and show the message when the download has finished.

using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Downloads
{
    public partial class Form1 : Form
    {
        private string destinationFolderPath;
        private Stopwatch stopwatch;

        // Event to signal when a download is completed
        public event EventHandler<DownloadEventArgs> DownloadCompleted;

        public Form1()
        {
            InitializeComponent();

            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100;
            progressBar2.Minimum = 0;
            progressBar2.Maximum = 100;

            // Subscribe to the event
            DownloadCompleted += OnDownloadCompleted;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            // Start the Marquee animation
            StartStopMarquee(true);

            var downloadFileUrl = "https://onlinetestcase.com/wp-content/uploads/2023/06/25-MB.wmv";
            destinationFolderPath = Path.GetFullPath(@"d:\");

            int numberOfDownloads = 200;

            for (int i = 1; i <= numberOfDownloads; i++)
            {
                string destinationFilePath = Path.Combine(destinationFolderPath, $"file_{i}.wmv");

                using (HttpClient client = new HttpClient())
                {
                    string url = downloadFileUrl;

                    // Move the declaration inside the loop
                    int downloadNumber = i;

                    var progress = new Progress<DownloadProgress>(dp => ReportProgress(dp.Percent, downloadNumber, dp.ExpectedSize, stopwatch.Elapsed, dp.TotalBytesRead));

                    // Offload the download operation to a separate thread
                    await DownloadFileAsync(client, url, destinationFilePath, progress, downloadNumber);
                }
            }
        }

        async Task DownloadFileAsync(HttpClient client, string url, string destination, IProgress<DownloadProgress> progress, int downloadNumber)
        {
            stopwatch = Stopwatch.StartNew();

            using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
            {
                response.EnsureSuccessStatusCode();

                using (var stream = await response.Content.ReadAsStreamAsync())
                using (var fileStream = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
                {
                    var buffer = new byte[8192];
                    long totalRead = 0;
                    int bytesRead;

                    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                    {
                        await fileStream.WriteAsync(buffer, 0, bytesRead);
                        totalRead += bytesRead;

                        // Report progress
                        var downloadProgress = new DownloadProgress
                        {
                            Percent = (int)((double)totalRead / response.Content.Headers.ContentLength.Value * 100),
                            DownloadNumber = downloadNumber,
                            ExpectedSize = response.Content.Headers.ContentLength.Value,
                            TotalBytesRead = totalRead
                        };

                        progress.Report(downloadProgress);
                    }
                }

                // Raise the DownloadCompleted event when the download is successful
                DownloadCompleted?.Invoke(this, new DownloadEventArgs(downloadNumber, destination));
            }
        }

        void ReportProgress(int percent, int downloadNumber, long expectedSize, TimeSpan elapsedTime, long totalBytesRead)
        {
            // Update the progress bar for the specific download
            BeginInvoke(new Action(() => progressBar1.Value = percent));

            // Check if the download has completed
            if (percent == 100)
            {
                string destinationFilePath = Path.Combine(destinationFolderPath, $"file_{downloadNumber}.wmv");

                // Check if the file exists
                if (File.Exists(destinationFilePath))
                {
                    // Optionally, check the file size against the expected size
                    long actualSize = new FileInfo(destinationFilePath).Length;

                    if (actualSize == expectedSize)
                    {
                        string status = $"Download {downloadNumber}: Download Completed successfully";
                        // Update a label or print to console, etc.
                        BeginInvoke(new Action(() => label1.Text = status));
                    }
                    else
                    {
                        string status = $"Download {downloadNumber}: Completed with size mismatch";
                        // Handle size mismatch (optional)
                        BeginInvoke(new Action(() => label1.Text = status));
                    }
                }
                else
                {
                    string status = $"Download {downloadNumber}: File does not exist";
                    // Handle file not found (optional)
                    BeginInvoke(new Action(() => label1.Text = status));
                }
            }
            else
            {
                // Calculate the download speed in bytes per second
                double speed = totalBytesRead / elapsedTime.TotalSeconds;

                // Calculate the remaining download size
                long remainingSize = expectedSize - totalBytesRead;

                // Calculate the remaining time in seconds
                double remainingTime = remainingSize / speed;

                // Format the download speed and remaining time into a human-readable format
                string formattedSpeed = FormatBytes(speed) + "/s";
                string formattedTime = TimeSpan.FromSeconds(remainingTime).ToString(@"hh\:mm\:ss");

                // Display the amount downloaded, percentage, remaining size, download speed, and remaining time
                string status = $"Download {downloadNumber}: {percent}% - Remaining: {FormatBytes(remainingSize)} / {FormatBytes(expectedSize)} - Speed: {formattedSpeed} - Time Remaining: {formattedTime}";

                // Update a label or print to console, etc.
                BeginInvoke(new Action(() => label1.Text = status));
            }
        }

        private string FormatBytes(double bytes)
        {
            string[] suffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
            int suffixIndex = 0;

            while (bytes >= 1024)
            {
                bytes /= 1024;
                suffixIndex++;
            }

            return $"{Math.Round(bytes, 2)} {suffixes[suffixIndex]}";
        }

        private void OnDownloadCompleted(object sender, DownloadEventArgs e)
        {
            stopwatch.Stop();
        }

        private void StartStopMarquee(bool start)
        {
            if (progressBar1.InvokeRequired)
            {
                // Use Invoke to marshal the call to the UI thread
                Invoke(new Action<bool>(StartStopMarquee), start);
            }
            else
            {
                if (start)
                {
                    // Start the Marquee animation
                    progressBar1.Style = ProgressBarStyle.Marquee;
                }
                else
                {
                    // Stop the Marquee animation
                    progressBar1.Style = ProgressBarStyle.Continuous;
                }
            }
        }

        private void UpdateProgressBar(int value)
        {
            if (progressBar1.InvokeRequired)
            {
                // Use Invoke to marshal the call to the UI thread
                Invoke(new Action<int>(UpdateProgressBar), value);
            }
            else
            {
                // Update the progress bar value
                progressBar1.Value = value;
                // Force the UI to update
                progressBar1.Refresh(); // or progressBar1.Invalidate();
            }
        }

        private class DownloadProgress
        {
            public int Percent { get; set; }
            public int DownloadNumber { get; set; }
            public long ExpectedSize { get; set; }
            public long TotalBytesRead { get; set; }
        }

        // Custom event arguments for download completion
        public class DownloadEventArgs : EventArgs
        {
            public int DownloadNumber { get; }
            public string DestinationFilePath { get; }

            public DownloadEventArgs(int downloadNumber, string destinationFilePath)
            {
                DownloadNumber = downloadNumber;
                DestinationFilePath = destinationFilePath;
            }
        }
    }
}
Developer technologies | Windows Forms
Developer technologies | C#
{count} votes

1 answer

Sort by: Most helpful
  1. Anonymous
    2023-12-12T05:54:00.1+00:00

    Hi @Daniel Lee , Welcome to Microsoft Q&A,

    Asynchronous operations in the loop are performed through await DownloadFileAsync, which may cause the UI thread to block. You can try using Task.Run to move the asynchronous operation to a background thread for execution, and then perform UI updates on the UI thread.

    private async void button1_ClickAsync(object sender, EventArgs e)
    {
        // Start the Marquee animation
        StartStopMarquee(true);
    
        var downloadFileUrl = "https://onlinetestcase.com/wp-content/uploads/2023/06/25-MB.wmv";
        destinationFolderPath = Path.GetFullPath(@"C:\xxx");
    
        int numberOfDownloads = 200;
    
        await Task.Run(async () =>
        {
            for (int i = 1; i <= numberOfDownloads; i++)
            {
                string destinationFilePath = Path.Combine(destinationFolderPath, $"file_{i}.wmv");
    
                using (HttpClient client = new HttpClient())
                {
                    string url = downloadFileUrl;
    
                    // Move the declaration inside the loop
                    int downloadNumber = i;
    
                    var progress = new Progress<DownloadProgress>(dp => ReportProgress(dp.Percent, downloadNumber, dp.ExpectedSize, stopwatch.Elapsed, dp.TotalBytesRead));
    
                    // Offload the download operation to a separate thread
                    await DownloadFileAsync(client, url, destinationFilePath, progress, downloadNumber);
                }
            }
        });
    }
    

    Moreover, the update of label1 is performed in BeginInvoke, which puts the update operation on the UI thread. However, the ReportProgress method is called on each iteration in the loop, which frequently updates the UI.

    Frequent UI updates can cause lags, especially when doing a lot of asynchronous operations in a loop. Each call to ReportProgress will trigger a UI update, which may cause the UI thread to block, resulting in lags.

    async Task DownloadFileAsync(HttpClient client, string url, string destination, IProgress<DownloadProgress> progress, int downloadNumber)
    {
        stopwatch = Stopwatch.StartNew();
    
        await Task.Run(async () =>
        {
            using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
            {
                response.EnsureSuccessStatusCode();
    
                using (var stream = await response.Content.ReadAsStreamAsync())
                using (var fileStream = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
                {
                    var buffer = new byte[8192];
                    long totalRead = 0;
                    int bytesRead;
    
                    while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                    {
                        await fileStream.WriteAsync(buffer, 0, bytesRead);
                        totalRead += bytesRead;
    
                        // Report progress
                        var downloadProgress = new DownloadProgress
                        {
                            Percent = (int)((double)totalRead / response.Content.Headers.ContentLength.Value * 100),
                            DownloadNumber = downloadNumber,
                            ExpectedSize = response.Content.Headers.ContentLength.Value,
                            TotalBytesRead = totalRead
                        };
    
                        // Update UI on the main thread
                        BeginInvoke(new Action(() => progress.Report(downloadProgress)));
                    }
                }
    
                // Raise the DownloadCompleted event when the download is successful
                DownloadCompleted?.Invoke(this, new DownloadEventArgs(downloadNumber, destination));
            }
        });
    }
    
    

    This modification uses BeginInvoke to marshal the UI update to the main thread.

    Best Regards,

    Jiale


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". 

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.