无限循环,同时使用WebClient下载多个文件

本文关键字:下载 文件 WebClient 无限循环 | 更新日期: 2023-09-27 18:03:02

概念:我正在制作一个从给定URL下载文件的C#应用程序。文本框,添加了URL,文件下载,每个事件都以正确的方式发生。

我正在尝试重新创建此程序以逐个下载多个文件。我有一个带有一个 url/行的文本框,解析正确进行,我将所有链接都放在文本框中的字符串数组中。然后它开始异步下载,我想让它只一个接一个地下载,所以我在 foreach 循环中做了一个 while 循环,因为我不想在当前 url 完成下载之前转到下一个 url。

问题是:我进入了一个无限循环(虽然我之前做了这项工作(idk how(,如果我在 while 循环中放置一个消息框(注意:我一分钟前重试,这次它没有解决问题((。

我只显示代码片段:

foreach (string url in urllist)
{
    isdonwloaded = false;
    string filename = url.Split('/').Last();
    label3.Text = filename;
    webclient.DownloadFileAsync(new Uri(url), @"C:'Users'Krisz" + @"'" + filename);
    while (!isdonwloaded) // this was the first idea, but with webclient.IsBusy it did the same thing
    {
        // MessageBox.Show(counter);
        Thread.Sleep(1000);
        label8.Text = "Download in progress...";
    }
    counter++;
    label8.Text = "Done!";
}
// Events:
webclient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webc_DownloadProgressChanged);
webclient.DownloadFileCompleted += new AsyncCompletedEventHandler(webc_DownloadFileCompleted);
// The DownloadFileCompleted event:
void webc_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
    label7.Text = String.Format("Files {0} / {1}", counter, arraylength(urllist));
    isdonwloaded = true;
}

我已经研究了这个线程: WebClient.DownloadFileAsync - 一次下载一个文件,但我也无法让它以这种方式工作。(也许我误解了什么?

有人可以给我一些提示,我做错了什么?我从来没有真正使用过事件,所以遇到错误只是时间问题。每一点帮助都得到了极大的赞赏,这个程序对我来说将是一个有用的程序。

无限循环,同时使用WebClient下载多个文件

好的,首先,了解为什么你的代码现在不起作用。

想象一下,你有一个办公室里有两个人。两者都有"盒子里"。他们的工作流程是:检查收件箱。如果收件箱中有任务,则他们执行任务直到完成,然后再次检查收件箱,重复。

工作人员 1 在他们的收件箱中收到一条消息,指出您的下一个任务是:

  • 关闭开关
  • 将标签更改为"正在下载">
  • 告诉工作人员 2 下载文件
  • 检查开关是否打开 - 如果打开,则断开环路;如果没有,则进入睡眠状态一秒钟。
  • 返回到上一步
  • 将标签更改为"完成">
  • 此任务现已完成
工作人员

1 关闭开关,并将以下任务放入工作人员 2 的收件箱中:

  • 下载文件
  • 告诉工作人员 1 打开开关
  • 此任务已完成

然后,工作人员 1 检查开关是否打开。事实并非如此,因此工人 1 进入睡眠状态。

工作人员

2 下载该文件,然后在工作人员 1 的收件箱中放置一条消息,内容如下:

  • 打开开关
  • 此任务已完成

现在你明白为什么工人 1 永远沉睡了,对吧? 该开关永远不会被翻转,因为工作线程 1 的工作是翻转该开关,而工作线程 1 在翻转之前处于睡眠状态。工作人员 1 在当前任务完成之前不会查看其收件箱,并且在切换该开关之前不会完成当前任务。

这给了我们一个解决方案的想法,但它不是一个好主意。

解决这个问题的廉价,肮脏,危险和不明智的方法是使用"DoEvents"而不是"睡眠"。这会将任务更改为:

  • 关闭开关
  • 将标签更改为"正在下载">
  • 告诉工作人员 2 下载文件
  • 检查开关是否打开 - 如果它断开,则断开循环;如果没有,则检查收件箱中的邮件并执行在其中找到的任何操作。
  • 返回到上一步
  • 将标签更改为"完成">
  • 此任务已完成

这解决了您的直接问题,但它引入了新的问题。我们现在不再有干净的工作流程;一个收件箱任务可以生成第二个收件箱任务,而第二个收件箱任务又可以生成第三个收件箱任务。任务可以成为"重入",其中一个任务最终会启动自身的第二个版本。此解决方案不优雅,会导致难以调试的情况。理想情况下,您希望收件箱任务具有在旧任务完成后启动新任务的属性,而不是在旧任务仍有工作要做时启动

对于

您的问题(如果您使用的是 C# 5(,一个更好的便宜和肮脏的解决方法是使用

await Task.Delay(1000);

而不是SleepDoEvents. 这会对工作流程进行微妙的更改。基本上它变成了:

  • 关闭开关
  • 将标签更改为"正在下载">
  • 告诉工作人员 2 下载文件
  • 检查开关是否打开
  • 如果处于打开状态,则将标签更改为"完成";此任务已完成。
  • 如果没有,请工人 3 在一秒钟内给我发送一个任务;这个任务就完成了。

如果工作人员 3 被告知向工作人员 1 发送新任务,则它发送的新任务是:

  • 检查开关是否打开
  • 如果处于打开状态,则将标签更改为"完成";此任务已完成。
  • 如果没有,请工人 3 在一秒钟内给我发送一个任务;这个任务就完成了。

您会看到这如何微妙但正确地改变工作流程吗? 现在,工作人员 1 将标签更改为正在下载,向工作人员 2 发送消息,检查开关,向工作人员 3 发送消息,然后返回到其收件箱。 工作线程 2 执行下载并向工作线程 1 发送消息。 工作人员 1 拨动开关并返回到收件箱。工作人员 3 向工作人员 1 发送消息。工作人员 1 检查开关,将标签更改为"完成",然后返回到收件箱。

现在,没有任务告诉您在收件箱中查找更多任务。每个收件箱任务都按顺序处理:较晚到达的任务始终在较早到达的任务完成后启动。

然而,最好的解决方案是拥有一个DownloadClientAsync版本,该版本本身可以返回可以等待的任务。不幸的是,它是无效返回的。构建一个特殊版本的 DownloadClientAsync 以返回可以等待的Task作为练习保留。一旦你有这样的帮助程序方法,那么代码就变得微不足道了;你只是await这个任务。

您是否尝试将事件订阅代码从当前行移动到以下行之后的行:

webclient.DownloadFileAsync(new Uri(url), @"C:'Users'Krisz" + @"'" + filename);
  webclient.DownloadFileCompleted -= new AsyncCompletedEventHandler(webc_DownloadFileCompleted); 
//Also it's a good practice to unsubscribe to event once after we are out-of-scope.
 webclient.DownloadFileCompleted += new AsyncCompletedEventHandler(webc_DownloadFileCompleted);

当我这样做时,其余的对我来说工作正常,否则屏幕是空白的,正如你所说的。

我认为这与BackgroundWorker class非常相似,其中CompleteEventHandler在主线程上运行。(我目前找不到任何证实这一点的结果。

这意味着您的主循环永远不会中断 - 您必须退出主 URL 循环并返回到 UI,然后webc_DownloadFileCompleted才能触发。

一种可能的解决方法是在第一个 URL 上运行单个 Web 客户端异步下载,然后返回到主 UI。webc_DownloadFileCompleted函数可以重新发出下一个异步下载调用。