Исключение обратного вызова System.Progress не может быть обработано - PullRequest
0 голосов
/ 27 апреля 2020

У меня есть этот модульный тест в C#

[Fact]
public async void DownloadAsync_ErrorDuringProgress_ThrowsException()
{
    using var model = SetupModel();
    SetDownload();

    async Task<DownloadStatus> Act()
    {
        return await model.DownloadAsync(VideoUrl, DestFile, (s, e) =>
        {
            e.Download.ProgressUpdated += (s, e) =>
            {
                throw new Exception("BOOM!");
            };
        });
    }

    // await Act();
    await Assert.ThrowsAsync<Exception>(Act);
}

Если в обработчике события возникает исключение, тогда весь метод должен генерировать исключение. (Другой вариант - обработать / игнорировать исключение и вернуть DownloadStatus.Failed, но затем я должен поместить универсальный блок c Catch, который запускает анализаторы, и ошибка в коде пользователя скрыта, когда она должна обрабатываться в событии. ).

Для другого подобного события оно работает как положено, но ProgressUpdated ведет себя по-другому. Если я вызываю «await Act ();», тогда выдается исключение ... но с ThrowsAsyn c невозможно отловить это исключение! Обратный вызов каким-то образом находится в другом контексте или что-то в этом роде.

Это класс System.Progress, который отвечает за запуск ProgressUpdated - это означает, что весь фрагмент кода обратного вызова выполняется в отдельном контексте, который не правильно обрабатывать исключения.

Глядя в документацию System.Process, я обнаружил, что это может быть причиной?

Любой обработчик, предоставленный конструктору или обработчикам событий, зарегистрированным в ProgressChanged событие вызывается через экземпляр SynchronizationContext, захваченный при создании экземпляра. Если во время создания нет текущего SynchronizationContext, обратные вызовы будут вызываться в ThreadPool.

Также не ясно, будет ли эта проблема возникать во время выполнения или только во время модульного тестирования.

Кто-нибудь понимает, что происходит, и как справиться с такой ситуацией? Я чувствую, что мне есть чему поучиться здесь.

Редактировать: Вот код, где произойдет переключение контекста

private async Task DownloadFileAsync(DownloadTaskFile fileInfo)
{
    Status = DownloadStatus.Downloading;
    using var cancelToken = new CancellationTokenSource();

    try
    {
        await _youTube.DownloadAsync(
            (IStreamInfo)fileInfo.Stream, fileInfo.Destination, new Progress<double>(ProgressHandler), cancelToken.Token).ConfigureAwait(false);

        void ProgressHandler(double percent)
        {
            fileInfo.Downloaded = (long)(fileInfo.Length * percent);
            UpdateProgress();

            if (IsCancelled)
            {
                try
                {
                    cancelToken.Cancel();
                }
                catch (ObjectDisposedException) { } // In case task is already done.
            }
        }
    }
    catch (HttpRequestException) { Status = DownloadStatus.Failed; }
    catch (TaskCanceledException) { Status = DownloadStatus.Failed; }
}

Где еще я услышал об этой проблеме необработанных ошибок? "asyn c void". Этот обработчик событий не asyn c, но он запускается asyn c.

1 Ответ

2 голосов
/ 29 апреля 2020

Progress<T> захватывает текущий SynchronizationContext при его создании и запускает свой обработчик событий в этом контексте. Обработчик для Progress<T> логически является обработчиком событий и обрабатывается как обработчик событий верхнего уровня.

Это означает, что исключения, распространяющиеся из обработчика прогресса, не могут быть перехвачены. Они всегда выполняются непосредственно в контексте.

Лучшее решение - это, вероятно, настроить свой API так, чтобы он принимал IProgress<T>, что является стандартным шаблоном . Progress<T> - не единственная реализация IProgress<T>, и, действительно, может показаться, что вам может понадобиться реализация IProgress<T>, которая непосредственно выполняет обработчики прогресса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...