【问题标题】:C# Async download with stopwatch but UI is freezing somewhere使用秒表进行 C# 异步下载,但 UI 在某处冻结
【发布时间】:2016-10-12 14:17:25
【问题描述】:

这是我用来下载文件然后计算剩余时间和 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;
        }
    }
    /// -------------------------

【问题讨论】:

  • 我没有看到任何会导致冻结的代码。在您的程序上使用分析器,几乎每个程序都有过滤 UI 冻结代码的功能。
  • @ScottChamberlain 好的,看起来文件的实际下载,异步下载的 CPU 使用率非常高。不知道为什么会这样做。
  • 大卫,请参考上一个问题。 stackoverflow.com/questions/27261797/… 下载进度的回调似乎经常被调用。而且由于您在该回调中进行了一些 UI 更新,因此可能会导致您的 UI 冻结。建议的答案似乎也适用于您的情况。

标签: c# multithreading winforms user-interface thread-safety


【解决方案1】:

尽管您正在调用异步下载函数,但 UI 线程可能由于各种原因而冻结。防止 UI 冻结的一种方法是从与 UI 不同的线程调用文件的下载。例如,您可以使用 BackgroundWorker 完成此操作,并通过 thread safe calls 安全地修改表单的控件,或者简而言之,使用 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;
        }
    }));
}

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 谢谢,我实际上已经在下载完成时进行了更改。我正在使用带计数器的系统,但这看起来也可以很好地工作。谢谢!
    猜你喜欢
    • 2015-03-22
    • 1970-01-01
    • 2014-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-19
    相关资源
    最近更新 更多