HttpClient.GetAsync 挂起,然后互联网在其执行过程中消失

Posted

技术标签:

【中文标题】HttpClient.GetAsync 挂起,然后互联网在其执行过程中消失【英文标题】:HttpClient.GetAsync hangs then internet disappears during its execution 【发布时间】:2019-06-10 20:25:06 【问题描述】:

我在一个循环中下载了许多小文件(每个大小约为 1 mb),我希望这个循环在互联网连接丢失的情况下中断。但是httpClient.GetAsync 挂起并且如果我在执行时关闭 wi-fi(或拔掉电源线)不会抛出异常。

如果我使用CancellationToken,它也不会被取消(如果它在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();

正在使用 line 从循环中调用它

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 并无休止地挂起。如果在有限的时间内没有返回值,我正在寻找一种中止它的方法。

【问题讨论】:

HttpClient 不应该在 60 秒后超时吗? 也许应该,但没有,我等了大约 10 分钟没有结果。 【参考方案1】:

我不能 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);
            
        
    

【讨论】:

清理相关的计时器我认为这一切都可以通过将timeout包装在using块中来避免...CancellationTokenSourceIDisposable@987654321 @. 1) 我捕获异常是有原因的,originalToken 用于将下载状态从“正在下载”设置为“已暂停”,并用于对用户的操作做出反应。而如果我们无法下载文件,则下载状态应变为“错误”。所以 'originalToken' 不应与 'timeout' 相关联并传递给 HttpClient(取消后我们可以再下载一个文件即可)。 2)这个解决方案看起来不错,我稍后会检查它。现在我用HttpWebRequest.Create(url)\HttpWebResponse.GetResponseAsync\response.GetResponseStream.ReadAsync替换了HttpClient,它被超时令牌取消了。

以上是关于HttpClient.GetAsync 挂起,然后互联网在其执行过程中消失的主要内容,如果未能解决你的问题,请参考以下文章

连接到 *** 时 HttpClient.GetAsync 超时

HttpClient.GetAsync,无效的 URI 异常

使用 HttpClient.GetAsync() 时如何确定 404 响应状态

HttpClient.GetAsync 一次只执行 2 个请求?

在 Azure Function 上获取数据时,HttpClient.GetAsync() 会产生 AggregateException

调试器在 Visual Studio 中的异步 HttpClient.GetAsync() 调用后停止