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

use*_*er1 5 c# error-handling webclient

我使用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)
}
Run Code Online (Sandbox Code Playgroud)

但我似乎无法将错误从我的匿名DownloadFileCompleted方法传递到我的主要捕获.这样做的正确方法是什么?

Eug*_*kal 5

重新抛出错误的解决方案

您可以将异常保存在 lambda 之外定义的某个变量中。然后它可以被重新抛出:

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

      mr.WaitOne();

      if (exception != null) throw exception;
}
Run Code Online (Sandbox Code Playgroud)

为什么不好?因为您将丢失堆栈跟踪(它将显示异常是在当前方法中抛出的,而不是在 WebClient 中)。尽管如此,如果您不需要或不关心堆栈跟踪,这是可能的解决方案。

就地处理异常

您还可以创建一些方法来处理外部 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);
}
Run Code Online (Sandbox Code Playgroud)

做对了

最好的主意是避免在 上使用 void 方法WebClient,因为您不能等待它们或应用一些continuation

此类方法在某种意义上很方便,但它们迫使您使用带有同步构造的秘密解决方案,以减少工作流对不同回调的依赖。

要使用 async-await,您必须应用public Task<byte[]> DownloadDataTaskAsync(Uri address)方法。

您可以:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

它会起作用,但我不确定这DownloadDataTaskAsync是一个真正的异步方法。

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);
}
Run Code Online (Sandbox Code Playgroud)

PS:public void DownloadFile(Uri address, string fileName)如果你不需要异步加载文件,你为什么不使用简单的阻塞方法?

  • @user1 您可以在 .Net 4 中使用 async/await。只需在 Nuget 中运行 `Install-Package Microsoft.Bcl.Async` 命令。有关更多详细信息,请参阅此 [链接](https://www.nuget.org/packages/Microsoft.Bcl.Async/) (2认同)