c# Async下载秒表,但UI冻结某处

本文关键字:UI 冻结 Async 下载 | 更新日期: 2023-09-27 18:12:40

下面是我用来下载文件并计算剩余时间和kbps的一些代码。然后,它将通过更新文本框和进度条将这些结果发布到表单上。我遇到的问题是UI正在冻结,我认为这可能是我使用秒表的方式,但不确定。有人有意见吗?

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

c# Async下载秒表,但UI冻结某处

UI线程可能由于各种原因冻结,尽管您正在调用异步下载函数。防止UI冻结的一种方法是从与UI不同的线程中调用文件下载。例如,你可以用BackgroundWorker来完成这个任务,并通过线程安全调用来安全地修改表单的控件,或者简单地说,用BeginInvoke()调用来包装在非ui线程中执行的代码。

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

关于你的代码的一些想法:

  • 当异常发生时不需要关闭秒表,秒表内部不做任何工作,它只是在您启动秒表时记住当前时间,并在访问经过的时间时计算差异。
  • 当您捕获所有异常时,不需要提供Exception类(即catch而不是catch(Exception))。
  • 仅在DownloadFileCompleted事件中标记下载完成,而不是在DownloadProgressChanged事件中标记下载完成,因为即使下载尚未完成,ProgressPercentage也可以为100。
  • 当使用异步代码时,在调用async方法之前,而不是之后,初始化状态变量(在您的情况下是Download_SuccessProgram.Downloading)总是更好。

现在大约冻结。DownloadProgreesChanged可以经常被WebClient触发,因此UI线程可以被更新消息淹没。您需要拆分报表进度并更新UI代码。UI应该定时更新,例如,每秒两次。下面是非常粗略的代码示例:

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