How to deal with exceptions when using WebClient.DownloadFileAsync

≯℡__Kan透↙ 提交于 2019-12-01 17:59:44

Bad solution with rethrowing

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

Exception exc = null;
using (WebClient wc = new WebClient())
{
      wc.DownloadFileCompleted += ((sender, args) =>
      ...

      mr.WaitOne();

      if (exception != null) throw exception;
}

Why is it bad? Because you will loose the stacktrace(it will show that the exception was thrown in the current method, not in the WebClient). Still, if you do not need or do not care about stacktrace, it is possible solution.

Handling the exception in place

You can also just create some method that will handle the exception in both the outer try-catch and in the downloaded 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);
}

Doing it right

The best idea is to avoid void methods on WebClient, because you can't await on them or apply some continuation.

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

To use async-await you will have to apply public Task<byte[]> DownloadDataTaskAsync(Uri address) method.

You can either:

1. await it to get the byte array of data to save it later manually, but it will require a solid rework in your application to make it async all the way:

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 am not sure that DownloadDataTaskAsync is a true async method.

2. So you may also consider using Task Continuations with the same method:

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

P.S.: And why don't you just use the simple blocking method public void DownloadFile(Uri address, string fileName) if you have no need to load files asynchronously?

Dudemanword

What you can do is create a Task (an async operation) and use the ContinueWith directive to handle the exception. This can be a bit 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 exposes a task based async download for you!

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

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 Event-based Asynchronous Pattern, you can't catch the exception, you can get exception throw AsyncCompletedEventArgs.Error Property

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