波莉不会超时

问题描述

我试图让 Polly 在 3 秒后超时以及返回某些 http 代码时再次尝试。但是,直到 HttpClient 超时 100 秒后才超时。

这是我的代码

private static Polly.Wrap.AsyncPolicyWrap<HttpResponseMessage> GetPolicy()
{
    var timeoutPolicy = Policy.TimeoutAsync(3,Polly.Timeout.TimeoutStrategy.Optimistic);

    var retryPolicy = Policy
        .Handle<HttpRequestException>()
        .OrResult<HttpResponseMessage>(r =>
            r.StatusCode == HttpStatusCode.TooManyRequests ||
            r.StatusCode == HttpStatusCode.ServiceUnavailable ||
            r.StatusCode == HttpStatusCode.Forbidden)
        .WaitAndRetryAsync(3,i => TimeSpan.FromSeconds(3));

    var policy = retryPolicy.WrapAsync(timeoutPolicy);
    return policy;
}

更新

根据要求,这里是我使用策略的代码

var pollyResponse = await GetPolicy().ExecuteAndCaptureAsync(() =>
      httpClient.SendAsync(GetMessage(HttpMethod.Delete,endpoint))
);

以及生成 HttpRequestMessage 的辅助方法

private HttpRequestMessage GetMessage<T>(HttpMethod method,string endpoint,T content)
{
    var message = new HttpRequestMessage
    {
        Method = method,RequestUri = new Uri(endpoint),Headers = {
                    { "MyCustomHeader",_value },{ HttpRequestHeader.Accept.ToString(),"application/json" }
                }
    };

    if (content != null)
    {
        var contentAsstring = JsonSerializer.Serialize(content);
        message.Content = new StringContent(contentAsstring);
    }

    return message;
}

解决方法

首先,让我与您分享您的GetPolicy的修订版:

private static IAsyncPolicy<HttpResponseMessage> GetStrategy()
{
    var timeoutPolicy = Policy
        .TimeoutAsync<HttpResponseMessage>(3,TimeoutStrategy.Optimistic,onTimeoutAsync: (_,__,___,____) =>
        {
            Console.WriteLine("Timeout has occurred");
            return Task.CompletedTask;
        });

    var retryPolicy = Policy
        .Handle<HttpRequestException>()
        .Or<TimeoutRejectedException>()
        .OrResult<HttpResponseMessage>(r =>
            r.StatusCode == (HttpStatusCode)429 ||
            r.StatusCode == HttpStatusCode.ServiceUnavailable ||
            r.StatusCode == HttpStatusCode.Forbidden)
        .WaitAndRetryAsync(3,i => TimeSpan.FromSeconds(3),onRetryAsync: (_,___) =>
        {
            Console.WriteLine("Retry will fire soon");
            return Task.CompletedTask;
        });

    return Policy.WrapAsync(retryPolicy,timeoutPolicy);
}
  • 我更改了返回类型,因为从消费者的角度来看,PolicyWrap 只是一个实现细节
    • 如果您不想使用接口 (AsyncPolicy<T>),也可以使用 IAsyncPolicy<T> 抽象类作为返回类型
  • 我添加了一些调试日志记录(onTimeoutAsynconRetryAsync),以便能够查看何时触发了哪些策略
  • 我在 Or<TimeoutRejectedException>() 上添加了一个 retryPolicy 构建器函数调用,以确保在超时时触发重试
  • 我还将您的 retryPolicy.WrapAsync 更改为 PolicyWrap 因为这样 escalation chain 更加明确
    • 最左边的策略是最外面的
    • 最正确的政策是最内在的
  • 我还更改了 timeoutPolicy (.TimeoutAsync ) 以符合重试策略(它们都包装了一个可能返回 {{1} })

为了能够测试我们的弹性策略(注意命名),我创建了以下辅助方法:

Task<HttpResponseMessage>
  • 它将向网站发出请求,该请求将在预定义的时间后返回指定的状态代码
    • 如果您之前没有使用过本网站,请访问:1,2

现在,让我们调用网站:

private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000,int responseCode = 200)
{
    return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}");
}

输出:

public static async Task Main()
{
    HttpResponseMessage response;
    try
    {
        response = await GetStrategy().ExecuteAsync(async () => await CallOverloadedAPI());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Environment.Exit(-1);
    }
    Console.WriteLine("Finished");
}

等等,什么??? 问题是没有触发任何政策。

为什么? 因为 5 秒后我们收到了 200 的响应。

但是,我们已经设置了超时时间,对吗? 是和否。 :) 即使我们已经定义了一个超时策略,我们还没有真正将它连接到 HttpClient

那么,我该如何连接? 好吧,通过Finished

因此,在超时策略的情况下,如果 CancellationToken 正在使用中,那么它可以调用其 CancellationToken 方法来向 HttpClient 指示超时事实。 HttpClient 会取消挂起的请求。

请注意,因为我们使用的是 TimeoutPolicy,所以例外是 Cancel,而不是 TimeoutRejectedException


所以,让我们修改我们的代码以接受 OperationCanceledException

CancellationToken

我们也必须调整使用方面:

public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000,int responseCode = 200,CancellationToken token = default)
{
    return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}",token);
}

现在输出看起来像这样:

public static async Task Main()
{
    HttpResponseMessage response;
    try
    {
        response = await GetStrategy().ExecuteAsync(async (ct) => await CallOverloadedAPI(token: ct),CancellationToken.None);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Environment.Exit(-1);
    }
    Console.WriteLine("Finished");
}

最后一行是 Timeout has occurred Retry will fire soon Timeout has occurred Retry will fire soon Timeout has occurred Retry will fire soon Timeout has occurred The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout. Message


请注意,如果我们从 TimeoutRejectedException 构建器中删除 Or<TimeoutRejectedException>() 调用,则输出将如下所示:

retryPolicy

所以,现在将触发重试。不会升级。


为了完整起见,这里是完整的源代码:

Timeout has occurred
The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.