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

6

所以,我一直在开发一个应用程序来使用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并不是最佳解决方案,如何改进我的代码将不胜感激!

2个回答

3

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

这非常有帮助,因为您可以轻松配置重试次数或超时时间,以及某些类型异常的后备逻辑。

另外,Scott Hanselman也有一篇非常有帮助的文章: https://www.hanselman.com/blog/AddingResilienceAndTransientFaultHandlingToYourNETCoreHttpClientWithPolly.aspx

我之前用过它,并且使我的代码变得非常干净和易于管理,因为所有策略都在一个地方,而不是作为http响应处理程序的一部分。如果需要,您还可以为每个不同的http请求器或客户端设置单独的策略。


太好了!经过简短的阅读,这篇文章似乎正是我所需要的,我会尽快实现它,谢谢! - callencx

1
这个问题的整个前提是,一个变得无响应的异步操作仍将通过提供的 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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接