简体   繁体   English

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

[英]How to download multiple files using webclient and threading?

I need to create multiple file downloaders using WebClient and threading, for learning purposes I created this code (below), the file is downloaded correctly but sometime the download stops and doesn't start running again. 我需要使用WebClient和线程创建多个文件下载器,出于学习目的,我创建了此代码(如下),文件已正确下载,但有时下载停止并且不再开始运行。 I also want to control the ProgressBar . 我也想控制ProgressBar Invoker of this code is a button. 此代码的调用者是一个按钮。

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

Your program doesn't work for several reasons: (I believe) Your are using several progress bars (one for every thread), but the WebClient doesn't provide any information which file caused the DownloadProgressChanged event to be fired, so you need one WebClient instance per file. 您的程序由于以下几个原因而无法运行:(我相信)您正在使用多个进度条(每个线程一个),但是WebClient不提供任何信息导致哪个文件触发了DownloadProgressChanged事件,因此您需要一个每个文件的WebClient实例。

Second when you pass the loop variable i to the wc.DownloadFileAsync(new Uri("http://ipv4.download.thinkbroadband.com/10MB.zip"), @"D:\\File" + Convert.ToString(i) + ".txt"); 其次,当您将循环变量i传递给wc.DownloadFileAsync(new Uri("http://ipv4.download.thinkbroadband.com/10MB.zip"), @"D:\\File" + Convert.ToString(i) + ".txt"); method, you pass a captured reference to the loop variable and not the value (because you are using a delegate referencing to a variable outside its scope, google for "Closure"), which means when the file starts downloading it may be copied to the wrong file. 方法中,您将捕获的引用传递给循环变量,而不是值(因为您使用的是引用其范围之外的变量的委托,google用于“关闭”),这意味着文件开始下载时,它可能会被复制到错误的文件。 You avoid this by passing the value to the delegate. 您可以通过将值传递给委托来避免这种情况。

An example, which is not exactly the same as your program but you can easily modify it: 一个示例,它与您的程序并不完全相同,但是您可以轻松地对其进行修改:

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

You're not holding onto a reference to your WebClient , so the Garbage Collector is technically able to reclaim it at pretty much any time after you finish starting the request. 您没有保留对WebClient的引用,因此从技术上讲,垃圾收集器可以在您完成请求后的几乎任何时间回收它。 You need to hold onto a reference to it somewhere so that the GC can't do this. 您需要在某处保留对它的引用,以使GC无法执行此操作。

Fortunately, this is a fairly solvable problem. 幸运的是,这是一个可以解决的问题。 We can just create a new class that will create a client for us while also storing it internally until it is disposed of: 我们可以创建一个新的类,该类将为我们创建一个客户端,同时还将其内部存储直到被处理:

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

Now all you need to do is make sure that you dispose of your web client when you're done with it (something you really should be doing anyway). 现在,您需要做的就是确保在处理完Web客户端后将其丢弃(无论如何,您确实应该这样做)。

Just add wc.DownloadFileCompleted += (s, args) => wc.Dispose(); 只需添加wc.DownloadFileCompleted += (s, args) => wc.Dispose(); when you attach your other handlers, so that you dispose of it when the file download is completed. 当您附加其他处理程序时,以便在文件下载完成后将其处理。

It's also worth noting that there is no need to create additional threads here at all. 还值得注意的是,这里根本不需要创建其他线程。 Your method is already inherently asynchronous; 您的方法已经天生就异步了。 it takes almost no (CPU) time to run. 运行几乎不需要(CPU)时间。 You can just remove all of your code to push it to another thread and lose nothing, without blocking your UI. 您只需删除所有代码即可将其推送到另一个线程,而不会丢失任何内容,而不会阻塞您的UI。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM