This is my whole source code. I want to download x files (lets assume we now have 2 files we want to download).
My main Point is to wait for all Downloads to complete and then proceed. I tried several points.
Task.WhenAll(List) -> Not working in my way of implementation
await -> impossibly in MainWindow function without async.
Workflow should be the following. Download started -> All Files Downloaded -> Show the Debug.Print
Atm: Source Code Output is -> All Downloads done "Download completed" "Download completed"
Should be: "Download completed" "Download completed" All Downloads done
Working for hours and no tiny step further to solve the problem. It is a WPF Application.
private volatile bool _download_completed; public bool DownloadCompleted { get { return _download_completed; } } public MainWindow() { InitializeComponent(); ProcessService.FillProcessData(); ProcessService.CloseAllProcesses(); DownloadHelper.AddDownloadFiles(); foreach(KeyValuePair<string,Uri> file in DownloadHelper.DownloadFiles) { DownloadFile(file.Key, file.Value); } Debug.Print("All Downloads done."); } private void DownloadFile(string Filename, Uri Uri) { Console.WriteLine(Filename + " " + Uri); _download_completed = false; WebClient client = new WebClient(); client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCompleted); client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressCallback); client.DownloadFileTaskAsync(Uri, Filename); }private void DownloadProgressCallback(object sender, DownloadProgressChangedEventArgs e) { this.Dispatcher.Invoke(() => { progressBar.Value = e.ProgressPercentage; StatusLabel.Content = e.ProgressPercentage + " % complete... ( " + e.BytesReceived + " / " + e.TotalBytesToReceive + ")"; }); } private void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { Console.WriteLine("Download completed"); this.Dispatcher.Invoke(() => { StatusLabel.Content = "Download Finished"; }); _download_completed = true; }
} 4 3 Answers
You need to use tasks to achieve this
First make your download function async:
private async TaskDownloadFile(string Filename, Uri Uri) { Console.WriteLine(Filename + " " + Uri); _download_completed = false; WebClient client = new WebClient(); client.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCompleted); client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressCallback); await client.DownloadFileTaskAsync(Uri, Filename); }Make the method async and await the DownloadFileTaskAsync method.
I would suggest to remove the events for DownloadFileCompeted and DownloadProgressChanged if you will do multiple downloads they don't make much sense.
Now that you have made the method async do the following:
var file1DownloadTask = TaskDownloadFile("filename","url");
var file2DownloadTask = TaskDownloadFile("filename","url");At this point your have two tasks:
You can use the Task.WaitAll(list_of_taks_goes_here)
Task.WaitAll(file1DownloadTask,file2DownloadTask )The wait all method will wait for all the tasks to finished so the next line of code is called after those two tasks have finished:
Then you can do change the UI or whatever you need to do after the Task.WaitAll line:
You could also do the following:
await TaskDownloadFile("filename","url");
await TaskDownloadFile("filename2","url");Which will wait for the downloads to finish before moving on to next line but this way you have to know all the files at the time your write the code, with the first method you could have a list of files from which you could build the tasks and pass them to Task.WaitAll.
Please note that the below will download the full download into memory, if the files being downloaded are very large consider re-writing to call the GetStreamAsync on HttpClient instead and stream to file, but this will do for reasonable sizes.
You generally want to re-use HttpClient, but dispose when done, so if this is WPF you could construct and keep around on startup and dispose at end, but this is probably also reasonable.
Edit: Note you may want to do this on another thread and dispatch completion back in case locking the UI thread is an issue when this is happening.
void Main()
{ using (var client = new HttpClient()) { var tasks = new List<Task>(); var files = new Dictionary<string, string>(); files.Add("c:\\temp\\MyFilename1.md", ""); files.Add("c:\\temp\\MyFilename2.md", ""); foreach (var file in files) { tasks.Add(DownloadFileAsync(client, file.Key, file.Value)); } Task.WaitAll(tasks.ToArray()); }
}
async Task DownloadFileAsync(HttpClient client, string filename, string url)
{ var contents = await client.GetStringAsync(url); File.WriteAllText(filename, contents);
} 6 My main Point is to wait for all Downloads to complete and then proceed.
I encourage you to take a step back and consider the UX design of your application. It's never a good idea to wait for I/O and block the UI thread while doing so.
In particular, your ViewModel constructors should never block on I/O. ViewModel constructors should complete immediately and synchronously, so that your UI can be shown immediately and synchronously.
Now, a constructor may start asynchronous operations; that's fine. It just shouldn't wait for them to complete. Your constructor should initialize the ViewModel into a state that says "Downloading..." or something, and when the downloads complete (or as they progress), the ViewModel can update its state to reflect that.
For more information on this pattern, see my MSDN article on data binding to asynchronous operations.
1