Вся предпосылка этой проблемы заключается в том, что асинхронная операция, которая перестала отвечать, все равно будет отвечать на запрос об отмене через предоставленный CancellationToken
. Я немного скептичен c о реальной применимости этого предположения, но в любом случае вот метод AwaitCancelRetry
, который автоматически отменяет и повторяет асинхронную операцию, если это занимает слишком много времени:
public static async Task<T> AwaitCancelRetry<T>(
Func<CancellationToken, Task<T>> function, TimeSpan timeout,
int maxAttempts, CancellationToken externalToken = default)
{
for (int i = 0; i < maxAttempts; i++)
{
using (var cts = CancellationTokenSource
.CreateLinkedTokenSource(externalToken, default))
{
cts.CancelAfter(timeout);
try
{
return await function(cts.Token); // Continue on captured context
}
catch (OperationCanceledException ex)
when (ex.CancellationToken == cts.Token
&& !externalToken.IsCancellationRequested)
{
continue;
}
}
}
throw new TimeoutException();
}
// Non generic version
public static Task AwaitCancelRetry(
Func<CancellationToken, Task> function, TimeSpan timeout,
int maxAttempts, CancellationToken externalToken = default)
=> AwaitCancelRetry<object>(
async ct => { await function(ct).ConfigureAwait(false); return null; },
timeout, maxAttempts, externalToken);
Пример использования:
private static HttpClient _httpClient;
public static Task<string> GetDataAsync(string url, CancellationToken token)
{
return AwaitCancelRetry(async ct =>
{
using (HttpResponseMessage response = await _httpClient.GetAsync(url, ct))
{
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
return null;
}
}, TimeSpan.FromSeconds(10), maxAttempts: 3, token);
}