我正在对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。
据我了解,第一个成功请求应取消所有未决的重试。有人可以指出我在做什么错吗?
答案 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
。