c# - 如何使用 dotnet core 和 Polly 添加动态重试策略
问题描述
我有一个 dotnet core (2.1) 控制台应用程序,我正在使用 Polly 用重试策略包装我的一段代码。这适用于如下所示的简单用例:
private void ProcessRun()
{
var policy = Policy.Handle<SocketException>().WaitAndRetryAsync(
retryCount: 3
sleepDurationProvider: attempt => TimeSpan.FromSeconds(10),
onRetry: (exception, calculatedWaitDuration) =>
{
Console.WriteLine($"Retry policy executed for type SocketException");
});
try
{
CancellationTokenSource _cts = new CancellationTokenSource()
PollyRetryWaitPolicy.ExecuteAsync(async token => {
MyOperation(token);
}, _cts.Token)
.ContinueWith(p => {
if (p.IsFaulted || p.Status == TaskStatus.Canceled)
{
Console.WriteLine("faulted or was cancelled");
}
})
.ConfigureAwait(false);
}
catch (Exception ex) {
Console.WriteLine($"Exception has occurred: {ex.Message}");
}
}
然后我使用以下代码对其进行测试:
private void MyOperation()
{
Thread.Sleep(2000);
throw new SocketException();
}
执行代码时,如预期的那样捕获了套接字异常。
我正在寻找一种灵活的方法来用多个策略而不是一个策略来包装我的代码。我更改了代码以将多个包装的 Polly 重试策略动态添加在一起,以允许捕获多个错误类型并轻松更改我正在寻找的异常。我将代码更改为:
internal PolicyWrap PollyRetryWaitPolicy;
public void AddRetryWaitPolicy<T>(int waitTimeInSeconds, int retryAttempts)
where T: Exception
{
// Setup the polly policy that will be added to the executing code.
var policy = Policy.Handle<T>().WaitAndRetryAsync(
retryCount: retryAttempts,
sleepDurationProvider: attempt => TimeSpan.FromSeconds(waitTimeInSeconds),
onRetry: (exception, calculatedWaitDuration) =>
{
Console.WriteLine($"Retry policy executed for type {typeof(T).Name}");
});
if (host.PollyRetryWaitPolicy == null)
{
// NOTE: Only add this timeout policy as it seems to need at least one
// policy before it can wrap! (suppose that makes sense).
var timeoutPolicy = Policy
.TimeoutAsync(TimeSpan.FromSeconds(waitTimeInSeconds), TimeoutStrategy.Pessimistic);
PollyRetryWaitPolicy = policy.WrapAsync(timeoutPolicy);
}
else
{
PollyRetryWaitPolicy.WrapAsync(policy);
}
}
private void ProcessRun()
{
AddRetryWaitPolicy<SocketException>(10, 5);
AddRetryWaitPolicy<InvalidOperationException>(5, 2);
try
{
Console.WriteLine($"Calling HostedProcess.Run() method. AwaitResult: {awaitResult}");
CancellationTokenSource _cts = new CancellationTokenSource()
PollyRetryWaitPolicy.ExecuteAsync(async token => {
MyOperation(token);
}, _cts.Token)
.ContinueWith(p => {
if (p.IsFaulted || p.Status == TaskStatus.Canceled)
{
Console.WriteLine("Process has faulted or was cancelled");
}
})
.ConfigureAwait(false);
}
catch (Exception ex) {
Console.WriteLine($"Exception has occurred: {ex.Message}");
}
}
当我使用此代码进行测试时,上面的代码按预期工作并重试了 5 次。
private void MyOperation()
{
Thread.Sleep(2000);
throw new SocketException();
}
但是当我尝试以下操作时,它不会按预期重试 2 次(它根本不会重试):
private void MyOperation()
{
Thread.Sleep(2000);
throw new InvalidOperationException();
}
我究竟做错了什么?以上所有内容的重点是根据我的需要动态地执行多个策略。除了 WrapPolicy,还有更好的方法吗?
提前感谢您的指点!
解决方案
这里:
if (host.PollyRetryWaitPolicy == null)
{
// NOTE: Only add this timeout policy as it seems to need at least one
// policy before it can wrap! (suppose that makes sense).
var timeoutPolicy = Policy
.TimeoutAsync(TimeSpan.FromSeconds(waitTimeInSeconds), TimeoutStrategy.Pessimistic);
PollyRetryWaitPolicy = policy.WrapAsync(timeoutPolicy);
}
else
{
PollyRetryWaitPolicy.WrapAsync(policy);
}
似乎if
分支和else
分支在包装中对重试策略和超时策略进行排序时采用了不一致的方法。
- 在
if
分支中,超时策略包含在重试策略中。因此超时充当每次尝试的超时。 - 在
else
分支中,任何现有的重试和超时策略都包装在新的重试策略之外。所以超时策略在新的重试策略 之外。- 新的重试策略设置为在之后重试
waitTimeInSeconds
;但超时策略也设置为在waitTimeInSeconds
. 因此,一旦发生第一次重试(对于第二次或后续配置的重试策略),超时就会中止整个执行。因此,正如您所观察到的,重试永远不会发生。
- 新的重试策略设置为在之后重试
要解决此问题,您可以将else
分支更改为:
PollyRetryWaitPolicy = policy.WrapAsync(PollyRetryWaitPolicy);
背景:请参阅PolicyWrap wiki 关于在 PolicyWrap 中排序策略的建议。根据您是放置TimeoutPolicy
在 a 内部还是外部RetryPolicy
,TimeoutPolicy
(在内部时)作为每次尝试超时,或(在外部时)作为所有尝试的总体超时。
推荐阅读
- asp.net - DropDownList 值作为外键插入数据库
- docker - Bamboo“运行 Docker 容器”插件从“等待服务启动”URL 中删除凭据
- makefile - pyenv:编译模块
- teradata - teradata 有没有办法从另一个表中替换一行中的多个字符串?
- python - ' HTML 编号显示 '
- javascript - 基本代码适用于除 IE 以外的所有浏览器
- amazon-web-services - AWS 中用于非常简单(转换)ETL 的最简单工具?
- sql - ssis 从 excel 导出到 sql,与列数无关
- javascript - 无法连接到我在 AWS 上运行的 Mongo 服务器。节点进程未启动,但 Mongod 日志看起来不错
- gradle - 如何更改/覆盖 rootDir/projectDir?