在完成之前经过一定时间后重新启动异步任务

Restart an async Task after certain time passes before completion

所以,我一直在开发一个使用 REST API 请求的应用程序,但是,由于某种原因,API 随机无响应(有时它会在 3 秒内给出响应并且有时请求会花费很长时间以致于抛出 TimeoutException) 所以每当我使用一个调用时,如果在一定时间内没有检索到响应,我会使用此代码重新启动调用:

bool taskCompletion = false;
while(taskCompletion == false)
{
    try
    {
        using (CancellationTokenSource cts = new CancellationTokenSource())
        {
            cts.CancelAfter(timeSpan);
            await task(cts.Token);
            taskCompletion = true;
        }
    }
    catch (OperationCanceledException)
    {
        taskCompletion = false;
    }
}

我的 API 请求之一如下:

public static async Task<Result> task(CancellationToken ct)
{
    string Url = baseurl
    ApiHelper instance = new ApiHelper();

    using (HttpResponseMessage response = await instance.ApiClient.GetAsync(Url, ct))
    {
        if (response.IsSuccessStatusCode)
        {
            var x = await response.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<ResultL>(x);
            if (result.result.Count() != 0)
                return result.result[0];
            else
                return null;
        }
        return null;
    }
}

不过,我不认为每次对代码使用的每个不同 API 请求使用 try-catch 是最好的解决方案,任何有关如何改进我的代码的帮助将不胜感激!

您是否考虑过使用容错库? .net 的一个例子是 Polly。 https://github.com/App-vNext/Polly

这很有用,因为您可以轻松配置重试计数或超时以及特定类型异常的回退逻辑。

斯科特·汉塞尔曼 (Scott Hanselman) 对此也发表了一篇很有帮助的文章: https://www.hanselman.com/blog/AddingResilienceAndTransientFaultHandlingToYourNETCoreHttpClientWithPolly.aspx

我以前用过它,它使我的代码超级干净且易于管理,因为所有策略都在一个地方,而不是 http 响应处理程序的一部分。如果需要,您还可以为每个不同的 http 请求者或客户端制定单独的策略。

这个问题的全部前提是已经变得无响应的异步操作仍然会通过提供的 CancellationToken 响应取消请求。我有点怀疑这个假设在现实生活中的适用性,但无论如何这里有一个 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 linkedCts = CancellationTokenSource
            .CreateLinkedTokenSource(externalToken);
        linkedCts.CancelAfter(timeout);
        try
        {
            return await function(linkedCts.Token); // Continue on captured context
        }
        catch (OperationCanceledException)
            when (linkedCts.IsCancellationRequested
                && !externalToken.IsCancellationRequested)
        {
            continue; // Retry
        }
    }
    throw new TimeoutException();
}

// Non generic version
public static Task AwaitCancelRetry(
    Func<CancellationToken, Task> function,
    TimeSpan timeout, int maxAttempts,
    CancellationToken externalToken = default)
{
    return 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);
}