Почему нет уведомлений об ошибках для UploadFileAsync с WebClient? - PullRequest
0 голосов
/ 06 мая 2018

Когда я выполняю следующий код:

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 не может его найти, но моя большая проблема в том, что если я вызову UploadFileAsync с тем же URI, метод просто выполняется так, как будто все в порядке.

Единственный признак того, что что-то не так, - это то, что ни один из двух обработчиков событий не вызывается. Я сильно подозреваю, что не получаю исключение, потому что асинхронный вызов не async/await, а основан на событии, но тогда я ожидал бы какое-то событие или свойство, которое указывает, что произошло исключение.

Как можно использовать код, который скрывает подобные ошибки, особенно сетевые ошибки, которые являются относительно распространенными в работе?

1 Ответ

0 голосов
/ 10 мая 2018

Почему нет уведомления об ошибке для UploadFileAsync с WebClient?

Цитирование Метод WebClient.UploadFileAsync (Uri, String, String) Примечания

Файл отправляется асинхронно с использованием ресурсов потоков, которые автоматически выделяются из пула потоков. Чтобы получить уведомление о завершении загрузки файла, добавьте обработчик события в событие UploadFileCompleted .

Акцент на шахте.

Вы не получаете ошибок, потому что он выполняется в другом потоке, чтобы не блокировать текущий поток. Чтобы увидеть ошибку, вы можете обратиться к ней в указанном обработчике событий через UploadFileCompletedEventArgs.Exception.

Мне было любопытно, почему используется WebClient, а не HttpClient, который уже является в основном асинхронным, но тогда мое предположение было связано с прогрессом загрузки.

Я бы предложил обернуть вызов WebClient обработчиками событий в Task, используя TaskCompletionSource, чтобы воспользоваться преимуществами TAP.

Ниже приведены примеры, приведенные здесь Как: обернуть шаблоны EAP в задачу

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 - это пользовательский метод расширения, используемый для переноса операций асинхронного шаблона на основе событий (EAP) WebClient как одной задачи с использованием TaskCompletionSource<TResult>.

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);
    }
}

Теперь это позволяет асинхронно вызывать загрузку и даже отслеживать прогресс с помощью собственного Отчета о ходе выполнения (необязательно)

Например, если на платформе 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

Так что теперь вы сможете загружать файл, не блокируя поток, и все же иметь возможность ожидать его, получать уведомления о ходе выполнения и обрабатывать исключения, если у вас есть попытка / отлов на месте.

...