为什么使用 WebClient 的 UploadFileAsync 没有错误通知?
Why no error notification for UploadFileAsync with WebClient?
当我执行以下代码时:
public static async Task UploadFile(string serverPath, string pathToFile, string authToken)
{
serverPath = @"C:\_Series\S1\The 100 S01E03.mp4";
var client = new WebClient();
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
client.UploadProgressChanged += UploadProgressChanged;
client.UploadFileCompleted += UploadCompletedCallback;
//client.UploadFileAsync(uri, "POST", pathToFile);
client.UploadFile(uri, "POST", pathToFile);
}
我得到异常:
System.Net.WebException: 'The remote server returned an error: (404)
Not Found.'
我不太担心 404,我正忙着寻找 WebClient
找不到它的原因,但我最担心的是,如果我用相同的 uri,该方法就像没有任何问题一样执行。
出现问题的唯一迹象是两个事件处理程序均未被调用。我强烈怀疑我没有得到异常,因为异步调用不是 async/await
而是基于事件的,但是我会期望某种事件或 属性 表明发生了异常。
如何在生产中使用隐藏此类错误的代码,尤其是相对更常见的网络错误?
Why no error notification for UploadFileAsync with WebClient?
引用 WebClient.UploadFileAsync Method (Uri, String, String) Remarks
The file is sent asynchronously using thread resources that are automatically allocated from the thread pool. To receive notification when the file upload completes, add an event handler to the UploadFileCompleted event.
强调我的。
您没有收到任何错误,因为它正在另一个线程上执行,以免阻塞当前线程。要查看错误,您可以通过 UploadFileCompletedEventArgs.Exception
.
在指定的事件处理程序中访问它
我很好奇为什么使用 WebClient
而不是 HttpClient
这已经主要是异步的,但后来我的假设是因为上传进度。
我建议使用 TaskCompletionSource
在 Task
中使用事件处理程序包装 WebClient
调用以利用 TAP。
以下与此处提供的示例类似How to: Wrap EAP Patterns in a Task
public static async Task UploadFileAsync(string serverPath, string pathToFile, string authToken, IProgress<int> progress = null) {
serverPath = @"C:\_Series\S1\The 100 S01E03.mp4";
using (var client = new WebClient()) {
// Wrap Event-Based Asynchronous Pattern (EAP) operations
// as one task by using a TaskCompletionSource<TResult>.
var task = client.createUploadFileTask(progress);
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
client.UploadFileAsync(uri, "POST", pathToFile);
//wait here while the file uploads
await task;
}
}
其中 createUploadFileTask
是一种自定义扩展方法,用于通过使用 TaskCompletionSource<TResult>
将 WebClient
的基于事件的异步模式 (EAP) 操作包装为一个任务。
private static Task createTask(this WebClient client, IProgress<int> progress = null) {
var tcs = new TaskCompletionSource<object>();
#region callbacks
// Specifiy the callback for UploadProgressChanged event
// so it can be tracked and relayed through `IProgress<T>`
// if one is provided
UploadProgressChangedEventHandler uploadProgressChanged = null;
if (progress != null) {
uploadProgressChanged = (sender, args) => progress.Report(args.ProgressPercentage);
client.UploadProgressChanged += uploadProgressChanged;
}
// Specify the callback for the UploadFileCompleted
// event that will be raised by this WebClient instance.
UploadFileCompletedEventHandler uploadCompletedCallback = null;
uploadCompletedCallback = (sender, args) => {
// unsubscribing from events after asynchronous
// events have completed
client.UploadFileCompleted -= uploadCompletedCallback;
if (progress != null)
client.UploadProgressChanged -= uploadProgressChanged;
if (args.Cancelled) {
tcs.TrySetCanceled();
return;
} else if (args.Error != null) {
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
} else
//since no result object is actually expected
//just set it to null to allow task to complete
tcs.TrySetResult(null);
};
client.UploadFileCompleted += uploadCompletedCallback;
#endregion
// Return the underlying Task. The client code
// waits on the task to complete, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
更进一步,创建另一种扩展方法来包装上传文件,使其可以等待...
public static Task PostFileAsync(this WebClient client, Uri address, string fileName, IProgress<int> progress = null) {
var task = client.createUploadFileTask(progress);
client.UploadFileAsync(address, "POST", fileName);//this method does not block the calling thread.
return task;
}
允许您的 UploadFile
重构为
public static async Task UploadFileAsync(string serverPath, string pathToFile, string authToken, IProgress<int> progress = null) {
using (var client = new WebClient()) {
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
await client.PostFileAsync(uri, pathToFile, progress);
}
}
现在您可以异步调用上传,甚至可以自己跟踪进度 Progress Reporting (Optional)
例如,如果在基于 XAML 的平台中
public class UploadProgressViewModel : INotifyPropertyChanged, IProgress<int> {
public int Percentage {
get {
//...return value
}
set {
//...set value and notify change
}
}
public void Report(int value) {
Percentage = value;
}
}
或使用开箱即用 Progress<T> Class
所以现在您应该能够在不阻塞线程的情况下上传文件,并且仍然能够等待它、获取进度通知和处理异常,前提是您有 try/catch 到位。
当我执行以下代码时:
public static async Task UploadFile(string serverPath, string pathToFile, string authToken)
{
serverPath = @"C:\_Series\S1\The 100 S01E03.mp4";
var client = new WebClient();
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
client.UploadProgressChanged += UploadProgressChanged;
client.UploadFileCompleted += UploadCompletedCallback;
//client.UploadFileAsync(uri, "POST", pathToFile);
client.UploadFile(uri, "POST", pathToFile);
}
我得到异常:
System.Net.WebException: 'The remote server returned an error: (404) Not Found.'
我不太担心 404,我正忙着寻找 WebClient
找不到它的原因,但我最担心的是,如果我用相同的 uri,该方法就像没有任何问题一样执行。
出现问题的唯一迹象是两个事件处理程序均未被调用。我强烈怀疑我没有得到异常,因为异步调用不是 async/await
而是基于事件的,但是我会期望某种事件或 属性 表明发生了异常。
如何在生产中使用隐藏此类错误的代码,尤其是相对更常见的网络错误?
Why no error notification for UploadFileAsync with WebClient?
引用 WebClient.UploadFileAsync Method (Uri, String, String) Remarks
The file is sent asynchronously using thread resources that are automatically allocated from the thread pool. To receive notification when the file upload completes, add an event handler to the UploadFileCompleted event.
强调我的。
您没有收到任何错误,因为它正在另一个线程上执行,以免阻塞当前线程。要查看错误,您可以通过 UploadFileCompletedEventArgs.Exception
.
我很好奇为什么使用 WebClient
而不是 HttpClient
这已经主要是异步的,但后来我的假设是因为上传进度。
我建议使用 TaskCompletionSource
在 Task
中使用事件处理程序包装 WebClient
调用以利用 TAP。
以下与此处提供的示例类似How to: Wrap EAP Patterns in a Task
public static async Task UploadFileAsync(string serverPath, string pathToFile, string authToken, IProgress<int> progress = null) {
serverPath = @"C:\_Series\S1\The 100 S01E03.mp4";
using (var client = new WebClient()) {
// Wrap Event-Based Asynchronous Pattern (EAP) operations
// as one task by using a TaskCompletionSource<TResult>.
var task = client.createUploadFileTask(progress);
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
client.UploadFileAsync(uri, "POST", pathToFile);
//wait here while the file uploads
await task;
}
}
其中 createUploadFileTask
是一种自定义扩展方法,用于通过使用 TaskCompletionSource<TResult>
将 WebClient
的基于事件的异步模式 (EAP) 操作包装为一个任务。
private static Task createTask(this WebClient client, IProgress<int> progress = null) {
var tcs = new TaskCompletionSource<object>();
#region callbacks
// Specifiy the callback for UploadProgressChanged event
// so it can be tracked and relayed through `IProgress<T>`
// if one is provided
UploadProgressChangedEventHandler uploadProgressChanged = null;
if (progress != null) {
uploadProgressChanged = (sender, args) => progress.Report(args.ProgressPercentage);
client.UploadProgressChanged += uploadProgressChanged;
}
// Specify the callback for the UploadFileCompleted
// event that will be raised by this WebClient instance.
UploadFileCompletedEventHandler uploadCompletedCallback = null;
uploadCompletedCallback = (sender, args) => {
// unsubscribing from events after asynchronous
// events have completed
client.UploadFileCompleted -= uploadCompletedCallback;
if (progress != null)
client.UploadProgressChanged -= uploadProgressChanged;
if (args.Cancelled) {
tcs.TrySetCanceled();
return;
} else if (args.Error != null) {
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
} else
//since no result object is actually expected
//just set it to null to allow task to complete
tcs.TrySetResult(null);
};
client.UploadFileCompleted += uploadCompletedCallback;
#endregion
// Return the underlying Task. The client code
// waits on the task to complete, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
更进一步,创建另一种扩展方法来包装上传文件,使其可以等待...
public static Task PostFileAsync(this WebClient client, Uri address, string fileName, IProgress<int> progress = null) {
var task = client.createUploadFileTask(progress);
client.UploadFileAsync(address, "POST", fileName);//this method does not block the calling thread.
return task;
}
允许您的 UploadFile
重构为
public static async Task UploadFileAsync(string serverPath, string pathToFile, string authToken, IProgress<int> progress = null) {
using (var client = new WebClient()) {
var uri = new Uri($"http://localhost:50424/api/File/Upload?serverPath={WebUtility.UrlEncode(serverPath)}");
await client.PostFileAsync(uri, pathToFile, progress);
}
}
现在您可以异步调用上传,甚至可以自己跟踪进度 Progress Reporting (Optional)
例如,如果在基于 XAML 的平台中
public class UploadProgressViewModel : INotifyPropertyChanged, IProgress<int> {
public int Percentage {
get {
//...return value
}
set {
//...set value and notify change
}
}
public void Report(int value) {
Percentage = value;
}
}
或使用开箱即用 Progress<T> Class
所以现在您应该能够在不阻塞线程的情况下上传文件,并且仍然能够等待它、获取进度通知和处理异常,前提是您有 try/catch 到位。