polly - 如何取消 Polly 的异步 WaitAndRetryPolicy 上的挂起重试
问题描述
我正在使用 polly 对 HTTP POST 请求进行简单的重试 n 次 szenario。它应该处理任何异常并重试将我的有效负载发布到 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。
据我了解,第一个成功的请求应该取消所有挂起的重试。谁能指出,我做错了什么?
解决方案
问题看起来是这一行:
response = await client.PostAsync(uri, content, cancellationToken);
正在使用一个名为 的变量cancellationToken
,而不是token
Polly 传递给在 处执行的委托的变量async token =>
。
使用以下内容应该可以解决它:
response = await client.PostAsync(uri, content, token);
解释
Polly 超时策略将超时结合CancellationToken
到调用者传递给执行的任何取消令牌中,但是要使该超时令牌产生任何效果,在执行的委托中,您必须使用 Polly 提供给执行的令牌(token
在这种情况下为变量)。
(从问题中发布的代码中,我们看不到任何信号取消cancellationToken
;如果有,请评论或编辑问题以澄清。)
使用代码client.PostAsync(uri, content, cancellationToken)
,如果没有取消cancellationToken
,那么每个 POST 都不会被取消,这可能解释了为什么您看到多个 POST 运行完成。
示范
我在您发布的代码附近制作了一个可运行的可重现示例,以进行演示。
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);
}
您可以在此处的 DotNetFiddle中运行它,并看到它通常会提供如下输出:
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!
),因为没有什么可以取消它们。
将指示的行改成 use 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(...)
确实有honor CancellationToken
s,所以可以使用效率稍高的TimeoutStrategy.Optimistic
。
推荐阅读
- reactjs - 多个条件在样式组件中不起作用
- c++ - 该程序将 .csv 文件中的一系列单词放入一个数组中,然后随机生成句子。但它不起作用
- javascript - 如何创建一个可以在所有 Vuejs 组件中使用的函数库?
- dynamics-crm - 保存父表单时为映射实体创建记录
- php - 301 重定向不适用于查询字符串 URL
- c# - 如何在 nunit TestCases 中使用元组?
- python-3.x - 如何定义一个将列表转换为字典的函数,同时在没有键值的情况下插入“无”(Python3)?
- python - 我试图在 python 中使用枕头模块打开图像
- android - Websocket 关闭代码:1006 Node Android okhttp3 AmazonEc2
- angular - 将 Angular 8 升级到 Angular 9 错误:TypeScript 编译中缺少 .ts