【问题标题】:Infinite loop while downloading multiple files with WebClient使用 WebClient 下载多个文件时无限循环
【发布时间】:2023-04-01 10:39:01
【问题描述】:

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

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

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

我将只显示代码 sn-p:

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 - Download files one at a time,但我无法让它以这种方式工作。 (也许我误解了什么?)

谁能给我一些提示,我做错了什么?我从来没有真正使用过事件,所以遇到错误只是时间问题。 非常感谢每一点帮助,这个程序对我来说很有用。

【问题讨论】:

标签: c# multithreading download webclient


【解决方案1】:

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

假设您在办公室里有两个人。两者都有“盒子”。他们的工作流程是:他们检查收件箱。如果收件箱中有任务,他们会执行任务直到完成,然后再次检查他们的收件箱,重复。

工人 1 在收件箱中收到一条消息,说明您的下一个任务是:

  • 关闭开关
  • 将标签更改为“正在下载”
  • 告诉 worker 2 下载文件
  • 检查开关是否打开——如果打开则跳出循环;如果没有,那就睡一秒钟。
  • 返回上一步
  • 将标签更改为“完成”
  • 此任务现已完成

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

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

Worker 1 然后检查开关是否打开。不是,所以工人 1 去睡觉了。

工人 2 下载文件,然后在工人 1 的收件箱中放入一条消息:

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

现在你明白为什么工人 1 永远沉睡了吧?该开关永远不会被翻转,因为工人 1 的工作是翻转该开关,而工人 1 正在睡觉直到它被翻转。工人 1 在当前任务完成之前不会查看他们的收件箱,并且当前任务在切换该开关之前不会完成。

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

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

  • 关闭开关
  • 将标签更改为“正在下载”
  • 告诉 worker 2 下载文件
  • 检查开关是否打开——如果打开则跳出循环;如果没有,请检查您的收件箱中的消息并执行您在其中找到的任何操作。
  • 返回上一步
  • 将标签更改为“完成”
  • 此任务已完成

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

对于您的问题(如果您使用的是 C# 5)更好的廉价和肮脏的解决方法是使用

await Task.Delay(1000);

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

  • 关闭开关
  • 将标签更改为“正在下载”
  • 告诉 worker 2 下载文件
  • 检查开关是否打开
  • 如果它已打开,则将标签更改为“完成”;此任务已完成。
  • 如果没有,请让工人 3 在一秒钟内向我发送任务;此任务已完成。

如果告诉工人 3 向工人 1 发送一个新任务,那么它发送的新任务是:

  • 检查开关是否打开
  • 如果它已打开,则将标签更改为“完成”;此任务已完成。
  • 如果没有,请让工人 3 在一秒钟内向我发送任务;此任务已完成。

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

现在没有任务告诉您在收件箱中查找更多任务按顺序处理每个收件箱任务:迟到的任务总是在先到的任务完成后开始。

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

【讨论】:

  • 哇,感谢您抽出宝贵时间。 :D 我必须重读几次,一旦我有 3 小时的空闲时间,我会坐下来把你的解释转换成源代码。直到现在:周末了!
  • 现在,我坐了下来,试图理解.. 你能告诉我它在代码中的样子吗?从未使用过 DoEvents 或等待。我会 - 当然 - 通过互联网照顾它,但拥有实时人工解释器会使事情变得更容易。 :)
【解决方案2】:

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

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

当我这样做时,其余的对我来说都很好,否则屏幕就像你说的那样是空白的。

【讨论】:

  • 我认为这可能是因为在线程上睡眠也处于循环中,直到你完成。如果您使用 C# 5(内置异步),最好实现 Eric 的建议。否则,您是否尝试在后台工作程序/单独线程上进行下载,因为根据定义,“DownloadFileAsync”不应阻止调用线程。
  • 所以,我必须在这个线程主题中游一下,因为我通常编写单线程应用程序。嗯,一个新的水平..(/me 启动 msdn)
【解决方案3】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-16
    相关资源
    最近更新 更多