C#使用秒表进行异步下载但UI在某处冻结

时间:2016-10-12 14:17:25

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

以下是我用来下载文件然后计算剩余时间和kbps的代码。然后它会通过更新文本框在表单上发布这些结果,并且它有一个进度条。我遇到的问题是用户界面很冷,我在想它可能是我如何使用秒表但不确定。有人有任何意见吗?

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

2 个答案:

答案 0 :(得分:2)

尽管您正在调用异步下载功能,但由于各种原因,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;
        }
    }));
}

答案 1 :(得分:1)

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

  • 当发生异常时,无需关闭秒表,秒表不会在里面做任何工作,它只记得你开始秒表时的当前时间,并在你访问经过的时间时计算差异。
  • 当您捕获所有异常时,不需要提供异常类(即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;
    }