异步文件下载与进度条

28

我试图让进度条的进度随着WebClient下载进度的变化而改变。这段代码仍然会下载文件,但当我调用startDownload()时,窗口会在下载文件时冻结。我希望用户能够在启动画面加载时看到进度的变化。有没有办法修复这个问题,使用户可以看到progressBar2的进度变化?

private void startDownload()
{
    WebClient client = new WebClient();
    client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
    client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
    client.DownloadFileAsync(new Uri("http://joshua-ferrara.com/luahelper/lua.syn"), @"C:\LUAHelper\Syntax Files\lua.syn");
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    double bytesIn = double.Parse(e.BytesReceived.ToString());
    double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
    double percentage = bytesIn / totalBytes * 100;
    label2.Text = "Downloaded " + e.BytesReceived + " of " + e.TotalBytesToReceive;
    progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
    label2.Text = "Completed";
}

如果窗口冻结了,那意味着该人正在 UI 线程下载,这意味着您的代码是同步的而非异步的。 - Joakim
我正在通过异步运行的后台工作程序调用 startDownload()。但这不应该导致窗口冻结,对吧? - Josh Ferrara
7
请不要在标题前加上"C#"等内容,这是标签的作用。 - John Saunders
2
只是一点提醒...你不需要计算百分比。DownloadProgressChangedEventArgs已经有e.ProgressPercentage供你使用了。 - David Sherret
嗨,David Sherret!e.ProgressPercentage 总是返回 0 值。你知道如何获取值吗?我已经将其分配为:progressBar.Value = e.ProgressPercentage,但不起作用。 - Zia Ur Rahman
4个回答

37

当您点击startDownload()时,UI线程将被冻结。如果您不想让表格被冻结,可以在另一个线程中使用startDownload()并在跨线程中更新进度。一种方法是,

private void startDownload()
{
    Thread thread = new Thread(() => {
          WebClient client = new WebClient();
          client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
          client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
          client.DownloadFileAsync(new Uri("http://joshua-ferrara.com/luahelper/lua.syn"), @"C:\LUAHelper\Syntax Files\lua.syn");
    });
    thread.Start();
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    this.BeginInvoke((MethodInvoker) delegate {
        double bytesIn = double.Parse(e.BytesReceived.ToString());
        double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
        double percentage = bytesIn / totalBytes * 100;
        label2.Text = "Downloaded " + e.BytesReceived + " of " + e.TotalBytesToReceive;
        progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
    });
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
    this.BeginInvoke((MethodInvoker) delegate {
         label2.Text = "Completed";
    }); 
}

阅读类似于 Google 的多线程编程,可以参考这里: http://msdn.microsoft.com/en-us/library/ms951089.aspx

-已修复 bgThread 声明中缺失的 ); 闭合符号。


我的程序中没有MethodInvoker,我该怎么办? - Dhru 'soni
3
BeginInvoke 在 WinForms 命名空间中。如果您正在使用 WPF,则应该使用 Dispatcher.BeginInvoke。 - Tsukasa
使用上述代码下载文件,但是我遇到了无法连接远程服务器的错误。 - Vinutha N
所以,代替这个语法,是什么?BeginInvoke((MethodInvoker) delegate { - GuardFromUA

17

您应该从UI线程中调用startDownload()WebClient.DownloadFileAsync()的整个想法是它会自动为您生成工作线程,而不会阻止调用线程。在startDownload()中,您指定了回调函数,这些回调函数修改了我假设是由UI线程创建的控件。因此,如果您从后台线程调用startDownload(),将会引起问题,因为线程只能修改其创建的UI元素。

它应该的工作方式是从UI线程中调用startDownload()startDownload()设置处理由UI线程处理的事件回调。然后异步启动下载并立即返回。当进度更改时,UI线程将收到通知,并且负责更新进度条控件的代码将在UI线程上执行,这样就不会有任何问题。


谢谢,我没有意识到我不能通过另一个异步线程启动下载。现在一切都正常了。 - Josh Ferrara

2
 public class ProgressEventArgsEx
{
    public int Percentage { get; set; }
    public string Text { get; set; }
}
public async static Task<string> DownloadStraingAsyncronous(string url, IProgress<ProgressEventArgsEx> progress)
{
    WebClient c = new WebClient();
    byte[] buffer = new byte[1024];
    var bytes = 0;
    var all = String.Empty;
    using (var stream = await c.OpenReadTaskAsync(url))
    {
        int total = -1;
        Int32.TryParse(c.ResponseHeaders[HttpRequestHeader.ContentLength], out total);
        for (; ; )
        {
            int len = await stream.ReadAsync(buffer, 0, buffer.Length);
            if (len == 0)
                break;
            string text = c.Encoding.GetString(buffer, 0, len);

            bytes += len;
            all += text;
            if (progress != null)
            {
                var args = new ProgressEventArgsEx();
                args.Percentage = (total <= 0 ? 0 : (100 * bytes) / total);
                progress.Report(args);
            }
        }
    }
    return all;
}
// Sample
private async void Bttn_Click(object sender, RoutedEventArgs e)
{
    //construct Progress<T>, passing ReportProgress as the Action<T> 
    var progressIndicator = new Progress<ProgressEventArgsEx>(ReportProgress);
    await TaskLoader.DownloadStraingAsyncronous(tbx.Text, progressIndicator);
}
private void ReportProgress(ProgressEventArgsEx args)
{
    this.statusText.Text = args.Text + " " + args.Percentage;
}

赞赏你提供了唯一使用 IProgress(Of T) 的答案。+1 - InteXX

1

我已经按照那篇文章的步骤操作,但是仍然遇到了同样的问题。 - Josh Ferrara
2
你是否在使用函数来显示后台线程的下载进度?如果是这样,你应该在UI线程中启动它,但将后台工作器的进度函数挂钩到它上面。希望这能有所帮助。我仍在阅读与你遇到的问题相关的内容,希望我能想出另一个解决方案。 - Stephen Tetreault

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接