Infinite loop while downloading multiple files with WebClient

我与影子孤独终老i 提交于 2019-12-05 20:44:28

OK, first off, understand why your code isn't working now.

Imagine you have two people in an office. Both have "in boxes". Their workflow is: they check their inbox. If there's a task in the inbox then they perform the task until it is finished, and then check their inbox again, repeat.

Worker 1 gets a message in their inbox that says your next task is:

  • turn the switch off
  • change the label to "downloading"
  • tell worker 2 to download the file
  • check to see if the switch is on -- if it is then break out of the loop; if not then go to sleep for one second.
  • go back to the previous step
  • change the label to "done"
  • this task is now finished

Worker 1 turns the switch off and puts the following task in worker 2's inbox:

  • download the file
  • tell worker 1 to turn the switch on
  • this task is finished

Worker 1 then checks to see if the switch is on. It is not, so worker 1 goes to sleep.

Worker 2 downloads the file, and then puts a message in worker 1's inbox that says:

  • turn the switch on
  • this task is finished

And now you see why worker 1 sleeps forever, right? That switch is never going to be flipped because it is worker 1's job to flip that switch, and worker 1 is sleeping until it is flipped. Worker 1 does not look in their inbox until the current task is finished, and the current task doesn't finish until that switch is flipped.

This gives us an idea for a solution, but it is not a good one.

The cheap, dirty, dangerous and ill-advised way to solve this problem is to use "DoEvents" instead of "Sleep". That changes the task to:

  • turn the switch off
  • change the label to "downloading"
  • tell worker 2 to download the file
  • check to see if the switch is on -- if it is then break out of the loop; if not then check your inbox for messages and do anything you find in there.
  • go back to the previous step
  • change the label to "done"
  • this task is finished

This solves your immediate problem but it introduces new problems. We now no longer have a clean workflow; one inbox task can spawn a second inbox task, which can in turn spawn a third inbox task. Tasks can become "re-entrant", where one task ends up starting a second version of itself. This solution is inelegant and makes for situations that are difficult to debug. Ideally you want your inbox tasks to have the property that a new one is started after the old one is finished, not while the old one still has work to do.

A better cheap-and-dirty fix for your problem (if you are using C# 5) is to use

await Task.Delay(1000);

instead of Sleep or DoEvents. That makes a subtle change to the workflow. Basically it becomes:

  • turn the switch off
  • change the label to "downloading"
  • tell worker 2 to download the file
  • check to see if the switch is on
  • if it is on then change the label to "done"; this task is finished.
  • if not then ask worker 3 to send me a task in one second; this task is finished.

If worker 3 is told to send worker 1 a new task, then the new task it sends is:

  • check to see if the switch is on
  • if it is on then change the label to "done"; this task is finished.
  • if not then ask worker 3 to send me a task in one second; this task is finished.

You see how that subtly but correctly changes the workflow? Now worker 1 changes the label to downloading, sends a message to worker 2, checks the switch, sends a message to worker 3, and then goes back to its inbox. Worker 2 does the download and sends a message to worker 1. Worker 1 flips the switch and goes back to the inbox. Worker 3 sends a message to worker 1. Worker 1 checks the switch, changes the label to done, and goes back to the inbox.

Now no task tells you to look in your inbox for more tasks. Each inbox task is processed in order: later-arriving tasks are always started after earlier-arriving tasks are finished.

The best solution however would be to have a version of DownloadClientAsync that could itself return a task that could be awaited. Unfortunately, it is void-returning. Building a special version of DownloadClientAsync that returns a Task that can be awaited is left as an exercise. Once you have such a helper method then the code becomes trivial; you just await that task.

Did you try shifting the event subscription code from current line to the line after :

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

When i did this the rest worked fine for me, otherwise the screen was blank as you told.

I think this is very similar to the BackgroundWorker class, where the CompletedEventHandler runs on the main thread. (I can't find any results confirming this at the moment.)

This means that your main loop will never be interrupted - you would have to exit your main URL loop and return to the UI before webc_DownloadFileCompleted can possibly fire.

One possible fix is to run a SINGLE webclient async download on your first URL, then return to the main UI. Your webc_DownloadFileCompleted function can re-issue the next async download call.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!