Polly 没有超时

Nie*_*nch 1 c# timeout dotnet-httpclient polly retry-logic

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

这是我的代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

更新

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

var pollyResponse = await GetPolicy().ExecuteAndCaptureAsync(() =>
      httpClient.SendAsync(GetMessage(HttpMethod.Delete, endpoint))
);
Run Code Online (Sandbox Code Playgroud)

以及生成 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;
}
Run Code Online (Sandbox Code Playgroud)

Pet*_*ala 5

首先,让我与您分享您的修订版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);
}
Run Code Online (Sandbox Code Playgroud)
  • 我更改了返回类型,因为从消费者的角度来看,这PolicyWrap只是一个实现细节
    • AsyncPolicy<T>如果您不想使用接口,也可以使用抽象类作为返回类型 ( IAsyncPolicy<T>)
  • 我添加了一些调试日志记录 ( onTimeoutAsynconRetryAsync),以便能够查看何时触发哪个策略
  • Or<TimeoutRejectedException>()在 上添加了一个构建器函数调用,retryPolicy以确保在超时时触发重试
  • 我还将您的策略​​更改retryPolicy.WrapAsyncPolicyWrap,因为升级链更加明确
    • 最左边的政策是最外面的
    • 最正确的政策是最内在的
  • 我还更改了timeoutPolicy(.TimeoutAsync < HttpResponseMessage > ) 以与重试策略保持一致(它们都包装了一个可能返回 a 的委托Task<HttpResponseMessage>

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

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}");
}
Run Code Online (Sandbox Code Playgroud)
  • 它将向网站发出请求,该请求将在预定义的时间后返回指定的状态代码
    • 如果您之前没有使用过本网站,请访问:1 , 2

现在,我们调用该网站:

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");
}
Run Code Online (Sandbox Code Playgroud)

输出:

Finished
Run Code Online (Sandbox Code Playgroud)

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

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

但是,我们已经设置了超时,对吗? 是和不是。:) 尽管我们已经定义了超时策略,但我们还没有真正将其连接到 HttpClient

那么,我该如何连接呢? 嗯,通过CancellationToken

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

请注意,因为我们使用 TimeoutPolicy,所以异常将是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);
}
Run Code Online (Sandbox Code Playgroud)

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

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");
}
Run Code Online (Sandbox Code Playgroud)

现在的输出将如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

最后一行是MessageTimeoutRejectedException


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

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}");
}
Run Code Online (Sandbox Code Playgroud)

因此,现在将触发重试。不会有升级。


为了完整起见,以下是整个源代码:

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");
}

private static AsyncPolicy<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);
}

private static HttpClient client = new HttpClient();
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);
}
Run Code Online (Sandbox Code Playgroud)