如何使用webclient和线程下载多个文件?

时间:2014-06-24 16:42:50

标签: c# multithreading webclient

我需要使用WebClient和线程创建多个文件下载程序,出于学习目的,我创建了此代码(如下所示),文件正确下载但有时下载停止并且不再开始运行。我也想控制ProgressBar。这个代码的调用者是一个按钮。

for (int i = 0; i < 3; i++)
{
    Start(
        delegate
        {
            WebClient wc = new WebClient();
            if(wc.IsBusy != true)
            {
                wc.DownloadFileAsync(new Uri("http://ipv4.download.thinkbroadband.com/10MB.zip"), @"D:\File" + Convert.ToString(i) + ".txt");
            wc.DownloadProgressChanged += 
                delegate(object sender1, DownloadProgressChangedEventArgs e1) {
                    foreach (ToolStripProgressBar it in statusStrip1.Items)
                    {
                        if ((it.Name == "pg" + Convert.ToString(x)) == true)
                        {
                            it.Control.Invoke((MethodInvoker)delegate() { it.Value = e1.ProgressPercentage; });
                        }
                    }
            };
            }
        });
}

public static Thread Start(Action action)
{
    Thread thread = new Thread(() => { action(); });
    thread.Start();
    return thread;
}

2 个答案:

答案 0 :(得分:2)

由于以下几个原因,您的计划不起作用: (我相信)您正在使用多个进度条(每个线程一个),但WebClient不提供任何导致 DownloadProgressChanged 事件被触发的文件的信息,因此您需要一个WebClient实例文件。

第二,当您将循环变量 i 传递给wc.DownloadFileAsync(new Uri("http://ipv4.download.thinkbroadband.com/10MB.zip"), @"D:\File" + Convert.ToString(i) + ".txt");方法时,您将捕获的引用传递给循环变量而不是值(因为您使用的是委托引用变量超出其范围,google为“Closure”),这意味着当文件开始下载时,它可能被复制到错误的文件中。您可以通过将值传递给委托来避免这种情况。

一个例子,它与你的程序不完全相同,但你可以很容易地修改它:

var progressBars = new ProgressBar[]
{
    this.progressBar1,
    this.progressBar2,
    this.progressBar3
};

for (int i = 0; i < 3; i++)
{
    var webClient = new WebClient();
    webClient.DownloadFileAsync(new Uri("http://ipv4.download.thinkbroadband.com/10MB.zip"), @"E:\File" + Convert.ToString(i) + ".txt");
    var progressBar = progressBars[i];
    webClient.DownloadProgressChanged += (sender1, e1) =>
    {
        progressBar.Invoke((MethodInvoker)(() =>
        {
            progressBar.Value = e1.ProgressPercentage;
        }));
    };
}

答案 1 :(得分:0)

您没有保留对WebClient的引用,因此垃圾收集器在完成启动请求后几乎可以在任何时候回收它。你需要在某处保留对它的引用,以便GC不能这样做。

幸运的是,这是一个相当可解决的问题。我们可以创建一个新类,它将为我们创建一个客户端,同时也将其存储在内部,直到它被处理掉:

public class ClientCreator
{
    private static HashSet<WebClient> clients = new HashSet<WebClient>();
    public static WebClient CreateClient()
    {
        WebClient client = new WebClient();
        lock (clients)
            clients.Add(client);
        client.Disposed += (s, args) =>
        {
            lock (clients) clients.Remove(client);
        };
        return client;
    }
}

现在你需要做的就是确保在你完成它之后处理你的网络客户端(无论如何你应该做的事情)。

添加其他处理程序时,只需添加wc.DownloadFileCompleted += (s, args) => wc.Dispose();,以便在文件下载完成后将其丢弃。

还值得注意的是,根本不需要在这里创建额外的线程。您的方法本质上是异步的;几乎没有(CPU)时间运行。您可以删除所有代码以将其推送到另一个线程而不会丢失任何内容,而不会阻止您的UI。