当我执行以下代码时:
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:'远程服务器返回错误:(404) 找不到。'
我不太担心404,我正忙着追查为什么WebClient
找不到它,但我最关心的是如果我用同一个uri打电话给UploadFileAsync
,该方法只是执行,好像什么都没有错。
唯一的错误指示是两个事件处理程序都没有被调用。我强烈怀疑我没有得到异常,因为异步调用不是async/await
而是基于事件,但是我会期望某种事件或属性指示发生了异常。
如何使用隐藏此类错误的代码,尤其是生产中相对较常见的网络错误?
答案 0 :(得分:5)
为什么没有针对WebClient的UploadFileAsync错误通知?
引用WebClient.UploadFileAsync Method (Uri, String, String) Remarks
使用从线程池自动分配的线程资源异步发送文件。 要在文件上传完成时收到通知,请将事件处理程序添加到UploadFileCompleted事件。
强调我的。
您没有错误,因为它正在另一个线程上执行,以便不阻止当前线程。要查看错误,您可以通过UploadFileCompletedEventArgs.Exception
。
我很好奇为什么使用已经主要是异步的WebClient
而不是HttpClient
,但我的假设是因为上传进度。
我建议使用WebClient
在Task
中使用事件处理程序包装TaskCompletionSource
调用以利用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
是一个自定义扩展方法,用于通过WebClient
将TaskCompletionSource<TResult>
的基于事件的异步模式(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。