使用WebClient.DownloadFileAsync时如何处理异常
How to deal with exceptions when using WebClient.DownloadFileAsync
我正在使用 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)
}
但我似乎无法将错误从我的匿名 DownloadFileCompleted
方法传递到我的主要捕获。正确的做法是什么?
你应该使用 await
和 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 使用 Event-based Asynchronous Pattern, you can't catch the exception, you can get exception throw AsyncCompletedEventArgs.Error 属性
您可以做的是创建一个任务(异步操作)并使用 ContinueWith 指令来处理异常。这可能有点不可读
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));
}
但是,随着 .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)
}
重新抛出的错误解决方案
您可以将异常保存在 lambda 外部定义的一些变量中。然后可以重新抛出:
Exception exc = null;
using (WebClient wc = new WebClient())
{
wc.DownloadFileCompleted += ((sender, args) =>
...
mr.WaitOne();
if (exception != null) throw exception;
}
为什么不好?因为您将丢失堆栈跟踪(它将显示异常是在当前方法中抛出的,而不是在 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);
}
做对了
最好的办法是避免 WebClient
, because you can't await on them or apply some continuation.
上的 void 方法
这些方法在某种意义上很方便,但它们迫使您使用具有同步结构的秘密解决方案,以减少工作流对不同回调的依赖。
要使用 async-await,您必须应用 public Task<byte[]> DownloadDataTaskAsync(Uri address)
方法。
您可以:
1. await
它获取数据的字节数组以便稍后手动保存,但需要在您的应用程序中进行大量返工才能使其成为 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)
}
}
它会起作用,但我不确定 DownloadDataTaskAsync
是否是真正的异步方法。
2. 所以你也可以考虑用Task Continuations,方法一样:
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.: 如果不需要异步加载文件,为什么不使用简单的阻塞方法public void DownloadFile(Uri address, string fileName)
?
我正在使用 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)
}
但我似乎无法将错误从我的匿名 DownloadFileCompleted
方法传递到我的主要捕获。正确的做法是什么?
你应该使用 await
和 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 使用 Event-based Asynchronous Pattern, you can't catch the exception, you can get exception throw AsyncCompletedEventArgs.Error 属性
您可以做的是创建一个任务(异步操作)并使用 ContinueWith 指令来处理异常。这可能有点不可读
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));
}
但是,随着 .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)
}
重新抛出的错误解决方案
您可以将异常保存在 lambda 外部定义的一些变量中。然后可以重新抛出:
Exception exc = null;
using (WebClient wc = new WebClient())
{
wc.DownloadFileCompleted += ((sender, args) =>
...
mr.WaitOne();
if (exception != null) throw exception;
}
为什么不好?因为您将丢失堆栈跟踪(它将显示异常是在当前方法中抛出的,而不是在 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);
}
做对了
最好的办法是避免 WebClient
, because you can't await on them or apply some continuation.
这些方法在某种意义上很方便,但它们迫使您使用具有同步结构的秘密解决方案,以减少工作流对不同回调的依赖。
要使用 async-await,您必须应用 public Task<byte[]> DownloadDataTaskAsync(Uri address)
方法。
您可以:
1. await
它获取数据的字节数组以便稍后手动保存,但需要在您的应用程序中进行大量返工才能使其成为 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)
}
}
它会起作用,但我不确定 DownloadDataTaskAsync
是否是真正的异步方法。
2. 所以你也可以考虑用Task Continuations,方法一样:
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.: 如果不需要异步加载文件,为什么不使用简单的阻塞方法public void DownloadFile(Uri address, string fileName)
?