如何取消对Polly的异步WaitAndRetryPolicy的挂起重试

时间:2019-07-26 07:45:48

标签: polly

我正在对HTTP POST请求使用polly进行简单的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);
    }

调用网络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。

据我了解,第一个成功请求应取消所有未决的重试。有人可以指出我在做什么错吗?

1 个答案:

答案 0 :(得分:0)

问题似乎在于此行:

const func =  me.onChange || (str => {});
func(str)

正在使用一个名为response = await client.PostAsync(uri, content, cancellationToken); 的变量,而不是Polly传递给cancellationToken处执行的委托的变量token

使用以下内容可以解决该问题:

async token =>

说明

Polly超时策略combine a timing-out CancellationToken into any cancellationToken the caller passes into the execution,但是为了使该超时令牌生效,在执行的委托中,您必须使用Polly提供给执行的令牌(在这种情况下,变量response = await client.PostAsync(uri, content, token);

(从问题中发布的代码中,我们看不到有任何迹象表明token被取消;如果有,请注释或编辑问题以进行澄清。)

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

演示

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

cancellationToken

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

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

该代码示例随机化以模拟不同程度的故障;您可能必须运行几次才能看到相似的结果。

显然可以放置多个呼叫(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(...),因此您可以使用效率更高的TimeoutStrategy.Optimistic