How to deal with exceptions when using WebClient.DownloadFileAsync

I download some files from the Internet using WebClient as follows:

 try { ManualResetEvent mr = new ManualResetEvent(false); mr.Reset(); using (WebClient wc = new WebClient()) { wc.DownloadFileCompleted += ((sender, args) => { if (args.Error == null) { File.Move(filePath, Path.ChangeExtension(filePath, ".jpg")); mr.Set(); } else { //how to pass args.Error? } }); wc.DownloadFileAsync(new Uri(string.Format("{0}/{1}", Settings1.Default.WebPhotosLocation, Path.GetFileName(f.FullName))), filePath); mr.WaitOne(); } } catch (Exception ex) { //Catch my error here and handle it (display message box) } 

But I cannot pass the error from my anonymous DownloadFileCompleted method to my main catch. What is the right way to do this?

+5
source share
3 answers

Bad Retreating Solution

You can save the exception in some variable defined outside of the lambda. Then it can be reset:

 Exception exc = null; using (WebClient wc = new WebClient()) { wc.DownloadFileCompleted += ((sender, args) => ... mr.WaitOne(); if (exception != null) throw exception; } 

Why is that bad? Since you will lose stacktrace (it will show that the exception was thrown in the current method and not in the WebClient). However, if you don't need or care about stacktrace, a solution is possible.

In-place exception handling

You can also just create some method that will handle the exception both in an external try-catch and in a loaded handler:

 void HandleWebClientException(Exception exc) { ... } try { ManualResetEvent mr = new ManualResetEvent(false); mr.Reset(); using (WebClient wc = new WebClient()) { wc.DownloadFileCompleted += ((sender, args) => { if (args.Error == null) { File.Move(filePath, Path.ChangeExtension(filePath, ".jpg")); mr.Set(); } else { HandleWebClientException(args.Error); } }); wc.DownloadFileAsync(new Uri(string.Format("{0}/{1}", Settings1.Default.WebPhotosLocation, Path.GetFileName(f.FullName))), filePath); mr.WaitOne(); } } catch (Exception ex) { HandleWebClientException(ex); } 

Correct execution

The best idea is to avoid void methods on WebClient because you cannot wait or apply some continuation .

Such methods are convenient in a sense, but they force you to use clandestine solutions with synchronization constructs to make the workflow less dependent on different callbacks.

To use async-wait, you need to use the public Task<byte[]> DownloadDataTaskAsync(Uri address) method.

You can:

1. await to get an array of data bytes in order to save it later manually, but this will require careful refinement of the asynchronous path in the application:

 public async Task LoadFile() { try { using (WebClient wc = new WebClient()) { var bytes = await wc.DownloadDataTaskAsync(new Uri(string.Format("{0}/{1}", Settings1.Default.WebPhotosLocation, Path.GetFileName(f.FullName))), filePath); System.IO.File.WriteAllBytes(bytes); // Probably turn it into async too } } catch (Exception ex) { //Catch my error here and handle it (display message box) } } 

It will work, but I'm not sure if DownloadDataTaskAsync is a true asynchronous programming method.

2. Thus, you can also use Continuations in the same way:

 public Task LoadFile() { Task<Byte[]> bytesTask = wc.DownloadDataTaskAsync(new Uri(string.Format("{0}/{1}", Settings1.Default.WebPhotosLocation, Path.GetFileName(f.FullName))), filePath); var success = bytesTask.ContinueWith((prev) => { System.IO.File.WriteAllBytes(prev.Result); }, TaskContinuationOptions.OnlyOnRanToCompletion); var failure = bytesTask.ContinueWith(prev => { MessageBox.Show //... }, TaskContinuationOptions.OnlyOnFaulted); return Task.WhenAny(success, failure); } 

PS: And why don’t you just use the simple public void DownloadFile(Uri address, string fileName) blocking method if you don’t have to upload files asynchronously?

+2
source

What you can do is create a task (async operation) and use the ContinueWith directive to handle the exception. It may be a little unreadable.

 using (WebClient wc = new WebClient()) { wc.DownloadFileCompleted += ((sender, args) => { if (args.Error == null) { File.Move(filePath, Path.ChangeExtension(filePath, ".jpg")); mr.Set(); } else { //how to pass args.Error? } }); Task.Factory.StartNew(() => wc.DownloadFile( new Uri(string.Format("{0}/{1}", Settings1.Default.WebPhotosLocation, Path.GetFileName(f.FullName))), filePath)) .ContinueWith(t => Console.WriteLine(t.Exception.Message)); } 

However, with the introduction of .NET 4.5, Webclient provides you with asynchronous task loading!

 using (WebClient wc = new WebClient()) { wc.DownloadFileCompleted += ((sender, args) => { if (args.Error == null) { File.Move(filePath, Path.ChangeExtension(filePath, ".jpg")); mr.Set(); } else { //how to pass args.Error? } }); wc.DownloadFileTaskAsync(new Uri(string.Format("{0}/{1}", Settings1.Default.WebPhotosLocation, Path.GetFileName(f.FullName))), filePath) .ContinueWith(t => t.Exception.Message) } 
+3
source

You should use await and DownloadFileTaskAsync :

 try { using (WebClient wc = new WebClient()) { await wc.DownloadFileTaskAsync(new Uri(string.Format("{0}/{1}", Settings1.Default.WebPhotosLocation, Path.GetFileName(f.FullName))), filePath); } } catch (Exception ex) { //Catch my error here and handle it (display message box) } 

DownloadFileAsync uses an Asynchronous event-based pattern , you cannot catch an exception, you can get a throw exception AsyncCompletedEventArgs.Error Property

+1
source

All Articles