HttpClient.GetAsync зависает, затем интернет исчезает во время его выполнения - PullRequest
0 голосов
/ 16 января 2019

Я загружаю много маленьких файлов (размером около 1 МБ) в цикле, и я хочу, чтобы этот цикл прервался в случае потери интернет-соединения. Но httpClient.GetAsync зависает и не исключение, если я выключаю Wi-Fi (или вынимаю шнур) во время его работы. Также он не отменяется, если я использую CancelationToken (если он отменен до httpClient.GetAsync, он отменяется, но мне нужно отменить его во время выполнения).

Мой опыт показывает, что если интернет исчезнет до вызова httpClient.GetAsync(url, cancellationToken);, он выдаст исключительную ситуацию, но если интернет исчезнет, ​​httpClient.GetAsync(url, cancellationToken); уже начнет работать, то он будет зависать бесконечно, и даже если я вызову, установите CancelationTokenSource.Cancel(), операция не будет отменена.

Функция с использованием HttpClient

        private static readonly HttpClient httpClient = new HttpClient();

        protected async Task<byte[]> HttpGetData(string url, CancellationToken cancellationToken)
        {
            var response = await httpClient.GetAsync(url, cancellationToken);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsByteArrayAsync();
        }

вызывается из цикла с использованием строки

byte[] data = await LimitedTimeAwaiter<byte[]>.Execute(
async (c) => { return await HttpGetData(chunkUrl, c); }, cancellationToken, 5);

пока здесь код LimitedTimeAwaiter

    public class LimitedTimeAwaiter<T>
    {
        public static async Task<T> Execute(Func<CancellationToken, Task<T>> function, CancellationToken originalToken, int awaitTime)
        {
            originalToken.ThrowIfCancellationRequested();

            CancellationTokenSource timeout = new CancellationTokenSource(TimeSpan.FromSeconds(awaitTime));
            try
            {
                return await function(timeout.Token);
            }
            catch (OperationCanceledException err)
            {
                throw new Exception("LimitedTimeAwaiter ended function ahead of time", err);
            }
        }
    }

Хотя я отменяю токен, данный httpClient.GetAsync, он не генерирует исключение OperationCanceledException и бесконечно зависает. Я ищу способ прервать его, если не возвращает значение в течение ограниченного периода времени.

1 Ответ

0 голосов
/ 17 января 2019

Я не на 100% уверен в том, что вы пытаетесь сделать, но в вашем LimitedTimeAwaiter есть недостаток.Вы на самом деле не предоставляете originalToken HttpClient, поэтому он не будет использоваться для отмены.Чтобы это исправить, вы должны связать свои два токена:

public class LimitedTimeAwaiter<T>
{
    public static async Task<T> Execute(Func<CancellationToken, Task<T>> function, CancellationToken originalToken, int awaitTime)
    {
        originalToken.ThrowIfCancellationRequested();

        var timeout = CancellationTokenSource.CreateLinkedTokenSource(originalToken);
        timeout.CancelAfter(TimeSpan.FromSeconds(awaitTime));

        try
        {
            return await function(timeout.Token);
        }
        catch (OperationCanceledException err)
        {
            throw new Exception("LimitedTimeAwaiter ended function ahead of time", err);
        }
    }
}

(также не совсем уверен, почему вы поймали исключение, но это не относится к делу).

Теперь я собираюсь предположитьваша проблема в том, что задание, возвращаемое HttpClient, не завершается, даже если вы отменили правильный токенВо-первых, вы должны сообщить об этом, потому что это будет ошибкой в ​​реализации HttpClient.Затем вы можете использовать этот обходной путь:

public class LimitedTimeAwaiter<T>
{
    public static async Task<T> Execute(Func<CancellationToken, Task<T>> function, CancellationToken originalToken, int awaitTime)
    {
        originalToken.ThrowIfCancellationRequested();

        using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(originalToken))
        {
            timeout.CancelAfter(TimeSpan.FromSeconds(awaitTime));

            try
            {
                var httpClientTask = function(timeout.Token);
                var timeoutTask = Task.Delay(Timeout.Infinite, timeout.Token); // This is a trick to link a task to a CancellationToken

                var task = await Task.WhenAny(httpClientTask, timeoutTask);

                // At this point, one of the task completed
                // First, check if we timed out
                timeout.Token.ThrowIfCancellationRequested();

                // If we're still there, it means that the call to HttpClient completed
                return await httpClientTask;
            }
            catch (OperationCanceledException err)
            {
                throw new Exception("LimitedTimeAwaiter ended function ahead of time", err);
            }
        }
    }
}
...