简体   繁体   English

使用WebClient.DownloadFileAsync时如何处理异常

[英]How to deal with exceptions when using WebClient.DownloadFileAsync

I am downloading some files from the internet using a WebClient in the following way:我正在使用WebClient通过以下方式从 Internet 下载一些文件:

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 seem to pass the error from my annonymous DownloadFileCompleted method up to my main catch.但是我似乎无法将错误从我的匿名DownloadFileCompleted方法传递到我的主要捕获。 What is the correct way to do this?这样做的正确方法是什么?

Bad solution with rethrowing重新抛出错误的解决方案

You can save the exception in some variable defined outside the lambda.您可以将异常保存在 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).因为您将丢失堆栈跟踪(它将显示异常是在当前方法中抛出的,而不是在 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:您还可以创建一些方法来处理外部 try-catch 和下载的处理程序中的异常:

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 .最好的想法是避免WebClient上的 void 方法,因为您不能等待它们或应用一些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.要使用 async-await,您必须应用public Task<byte[]> DownloadDataTaskAsync(Uri address)方法。

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 : 1. await它获取数据的字节数组以稍后手动保存它,但它需要在您的应用程序中进行可靠的返工以使其一直异步

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.它会起作用,但我不确定DownloadDataTaskAsync是否是真正的异步方法。

2. So you may also consider using Task Continuations with the same method: 2.所以你也可以考虑用同样的方法使用任务延续

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 blocking method public void DownloadFile(Uri address, string fileName) if you have no need to load files asynchronously? PS:如果不需要异步加载文件,为什么不使用简单的阻塞方法public void DownloadFile(Uri address, string fileName)呢?

What you can do is create a Task (an async operation) and use the ContinueWith directive to handle the exception.您可以做的是创建一个任务(异步操作)并使用 ContinueWith 指令来处理异常。 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!但是,随着 .NET 4.5 的引入,Webclient 为您公开了基于任务的异步下载!

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 :您应该使用awaitDownloadFileTaskAsync

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 DownloadFileAsync 使用基于事件的异步模式,你不能捕捉异常,你可以得到异常 throw AsyncCompletedEventArgs.Error属性

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM