【问题标题】:How to cancel pending retries on Polly's async WaitAndRetryPolicy如何在 Polly 的异步 WaitAndRetryPolicy 上取消挂起的重试
【发布时间】:2019-12-04 12:36:34
【问题描述】:

我正在使用 polly 对 HTTP POST 请求进行简单的 n 次重试。它应该处理任何异常并重试将我的有效负载发布到 api 端点 n 次。因此,我使用 WaitAndRetryPolicy 包装 TimoutPolicy 和悲观策略,每次尝试超时。两者都是异步策略。

当重试案例发生时,每一次重试尝试都会在重新建立连接后发布到端点。

包装这两个策略的方法:


    public static PolicyWrap WaitAndRetryNTimesWithTimeoutPerTry(int n, TimeSpan sleepDuration, TimeSpan retryTimeout)
    {
        var waitAndRetryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(
        retryCount: n,
            sleepDurationProvider: attempt => sleepDuration,
            onRetry: (exception, waitDuration, ctx) =>
            {
                Debug.WriteLine($"[Polly.OnRetry due '{exception.Message}'; waiting for {waitDuration.TotalMilliseconds} ms before retrying.");
            }
        );

        var timeoutPerTryPolicy = Policy.TimeoutAsync(
            retryTimeout, TimeoutStrategy.Pessimistic);

        return waitAndRetryPolicy.WrapAsync(timeoutPerTryPolicy);
    }

调用web api的代码:


    var waitAndRetry5TimesWithShortTimeout = ResiliencePolicyFactory.WaitAndRetryNTimesWithTimeoutPerTry(
        n: 5,
        sleepDuration: TimeSpan.FromMilliseconds(700),
        retryTimeout: TimeSpan.FromMilliseconds(2300));
        }

    try
    {
        await waitAndRetry5TimesWithShortTimeout.ExecuteAndCaptureAsync(async token =>
        {
            if (!cancellationToken.IsCancellationRequested)
            {
                response = await client.PostAsync(uri, content, cancellationToken);
                if (response.IsSuccessStatusCode)
                {
                    Debug.WriteLine($"[{nameof(CheckinService)}] ===>> Now Checked in!");
                }
            }
        }, cancellationToken);
    }
    catch(Exception ex)
    {
        throw new ApplicationException("NoCheckInPossible", ex);
    }

当代码遇到重试情况并在多次重试后成功时,每次重试尝试都会发布到端点,尽管我将取消令牌传递给 ExecuteAsync-Task 和 HttpClient。

据我了解,第一个成功的请求应该取消所有挂起的重试。谁能指出我做错了什么?

【问题讨论】:

  • ExecuteAndCaptureAsync(...) 内,使用Polly 在执行委托时将传入的令牌(在您的情况下为token),以便超时工作。在发布的代码中,该委托使用您自己的变量cancellationToken(我看不到是否有任何取消;当然超时不会)。如果这可以为​​您解决问题,我可以将其写为正确的答案。
  • 另外:TimeoutStrategy.Pessimistic 设计用于执行委托不兑现取消令牌的情况。 HttpClient 确实尊重这些令牌,因此您应该能够使用更简单的 TimeoutStrategy.Optimistic
  • TimeoutStrategy.Pessimistic only walks away from timed-out executions,这可能是您看到早期尝试继续完成的原因 - 您实际上并没有取消它们(可能是由于使用了错误的取消令牌;请参阅第一条评论),所以他们会如果在HttpClient 自己的超时(默认值:30 秒)之前重新建立底层连接,也完成。

标签: polly


【解决方案1】:

问题看起来是这一行:

response = await client.PostAsync(uri, content, cancellationToken);

正在使用一个名为 cancellationToken 的变量,而不是 Polly 传递给 async token =&gt; 处已执行委托的变量 token

使用以下应该可以解决它:

response = await client.PostAsync(uri, content, token);

说明

Polly 超时策略 combine a timing-out CancellationToken into any cancellationToken the caller passes into the execution,但要使该超时令牌产生任何效果,在执行的委托中,您必须使用 Polly 提供给执行的令牌(在本例中为变量 token)。

(从问题中发布的代码中,我们看不出有任何迹象表明取消了cancellationToken;如果有,请评论或编辑问题以澄清。)

使用代码 client.PostAsync(uri, content, cancellationToken),如果没有取消 cancellationToken,则每个 POST 都不会被取消,这可能解释了为什么您会看到多个 POST 运行完成。

演示

我在您发布的代码附近创建了一个可运行的reproducible example,以进行演示。

public static Random rand = new Random();

public static async Task Main()
{

    var waitAndRetry5TimesWithShortTimeout = WaitAndRetryNTimesWithTimeoutPerTry(
        n: 5,
        sleepDuration: TimeSpan.FromMilliseconds(70),
        retryTimeout: TimeSpan.FromMilliseconds(230));

    CancellationToken cancellationToken = new CancellationTokenSource().Token;

    string response;
    try
    {
        await waitAndRetry5TimesWithShortTimeout.ExecuteAndCaptureAsync(async token =>
        {
            Console.WriteLine("Placing call");
            if (!cancellationToken.IsCancellationRequested)
            {
                response = await PretendPostAsync(cancellationToken); // Change 'cancellationToken' to 'token' here, and it will start to work as expected.
                if (response == "success")
                {
                    Console.WriteLine($"Now Checked in!");
                }
            }
        }, cancellationToken);
    }
    catch(Exception ex)
    {
        throw new ApplicationException("NoCheckInPossible", ex);
    }

}

public static async Task<string> PretendPostAsync(CancellationToken token)
{
    if (rand.Next(4) != 0)
    {
        await Task.Delay(TimeSpan.FromSeconds(0.5), token);
    }

    return "success";
}

public static AsyncPolicyWrap WaitAndRetryNTimesWithTimeoutPerTry(int n, TimeSpan sleepDuration, TimeSpan retryTimeout)
{
    var waitAndRetryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(
    retryCount: n,
        sleepDurationProvider: attempt => sleepDuration,
        onRetry: (exception, waitDuration, ctx) =>
        {
            Console.WriteLine($"[Polly.OnRetry due '{exception.Message}'; waiting for {waitDuration.TotalMilliseconds} ms before retrying.");
        }
    );

    var timeoutPerTryPolicy = Policy.TimeoutAsync(
        retryTimeout, TimeoutStrategy.Pessimistic);

    return waitAndRetryPolicy.WrapAsync(timeoutPerTryPolicy);
}

您可以run this in DotNetFiddle here 并看到它通常会提供如下输出:

Placing call
[Polly.OnRetry due 'The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.'; waiting for 70 ms before retrying.
Placing call
Now Checked in!
[Polly.OnRetry due 'The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.'; waiting for 70 ms before retrying.
Placing call
Now Checked in!

(代码示例随机化以模拟不同程度的故障;您可能需要运行几次才能看到类似的结果。)

多个调用清楚地被放置 (Placing call),并且多次运行完成 (Now Checked in!),因为没有任何东西可以取消它们。

将指示的行改成token,可见即使多次调用,前面的尝试也被取消,只有一次成功。

Placing call
[Polly.OnRetry due 'The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.'; waiting for 70 ms before retrying.
Placing call
[Polly.OnRetry due 'The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.'; waiting for 70 ms before retrying.
Placing call
Now Checked in!

整理

因为HttpClient.PostAsync(...) 确实尊重CancellationTokens,所以您可以使用效率稍高的TimeoutStrategy.Optimistic

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-19
    • 1970-01-01
    相关资源
    最近更新 更多