为什么没有WebClient的UploadFileAsync错误通知?

时间:2018-05-06 09:11:23

标签: c# events exception-handling 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:'远程服务器返回错误:(404)   找不到。'

我不太担心404,我正忙着追查为什么WebClient找不到它,但我最关心的是如果我用同一个uri打电话给UploadFileAsync ,该方法只是执行,好像什么都没有错。

唯一的错误指示是两个事件处理程序都没有被调用。我强烈怀疑我没有得到异常,因为异步调用不是async/await而是基于事件,但是我会期望某种事件或属性指示发生了异常。

如何使用隐藏此类错误的代码,尤其是生产中相对较常见的网络错误?

1 个答案:

答案 0 :(得分:5)

  

为什么没有针对WebClient的UploadFileAsync错误通知?

引用WebClient.UploadFileAsync Method (Uri, String, String) Remarks

  

使用从线程池自动分配的线程资源异步发送文件。 要在文件上传完成时收到通知,请将事件处理程序添加到UploadFileCompleted事件

强调我的。

您没有错误,因为它正在另一个线程上执行,以便不阻止当前线程。要查看错误,您可以通过UploadFileCompletedEventArgs.Exception

在指定的事件处理程序中访问它

我很好奇为什么使用已经主要是异步的WebClient而不是HttpClient,但我的假设是因为上传进度。

我建议使用WebClientTask中使用事件处理程序包装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是一个自定义扩展方法,用于通过WebClientTaskCompletionSource<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。