HttpClient.GetAsync挂起,然后Internet在执行过程中消失

时间:2019-01-16 17:28:32

标签: c# http .net-core async-await cancellationtokensource

我正在一个周期内下载许多小文件(每个文件的大小约为1 mb),并且我希望在失去互联网连接的情况下中断此周期。但是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 :(得分:0)

我不确定您要做什么,但您的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);
            }
        }
    }
}