HttpClient.GetAsync 挂起然后互联网在执行期间消失
HttpClient.GetAsync hangs then internet disappears during its execution
我正在一个周期内下载大量小文件(每个文件大小约为 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();
}
正在使用行从循环中调用
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
并无休止地挂起。如果 return 在有限的时间内没有值,我正在寻找一种方法来中止它。
我不是 100% 确定您要尝试做什么,但您的 LimitedTimeAwaiter 中存在缺陷。您实际上并未将 originalToken
提供给 HttpClient,因此它不会用于取消。要解决这个问题,您应该 link 您的两个令牌:
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);
}
}
}
}
我正在一个周期内下载大量小文件(每个文件大小约为 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();
}
正在使用行从循环中调用
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
并无休止地挂起。如果 return 在有限的时间内没有值,我正在寻找一种方法来中止它。
我不是 100% 确定您要尝试做什么,但您的 LimitedTimeAwaiter 中存在缺陷。您实际上并未将 originalToken
提供给 HttpClient,因此它不会用于取消。要解决这个问题,您应该 link 您的两个令牌:
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);
}
}
}
}