这是我的Polly实现,它有两个策略,一个超时和一个重试。这个想法是,当sql超时时,超时时间将变得更长,因此sql server有更多的时间来完成工作。
然而,当使用需要几分钟才能完成的sp来模拟超时时,我没有看到超时策略被触发3次(要么附加一个调试器,要么只是搜索输出日志)。它发射一次,然后TimeoutRejectedException
将被抛出。
var timeoutPerTry = Policy
.TimeoutAsync(context =>
{
////enlarge timeout every time it happens
taskTimeoutInSeconds = (int)(timeoutMs / 1000);
Log.LogVerbose(
$"log something");
return TimeSpan.FromMilliseconds(timeoutMs);
}, TimeoutStrategy.Optimistic);
// retry SqlException up to MaxRetries
var retryPolicy = Policy
.Handle<SqlException>()
.RetryAsync(Constants.MaxRetries,
(response, calculatedWaitDuration, context) =>
{
Log.LogError(
$"Failed dynamic execution attempt. Retrying. {response.Message} - {response.StackTrace}");
});
try
{
////combine timeout policy and retry policy
var combinedPolicy = retryPolicy.WrapAsync(timeoutPerTry);
// ReSharper disable once AccessToDisposedClosure
var results =
await combinedPolicy.ExecuteAsync<IEnumerable<T>>(async () => {
var connectionString = ConnectionStringHelper.GetConnectionString(warehouseId);
using (var connection = new SqlConnection(connectionString)) // assumed no need for using block as closed by caller
{
await connection.OpenAsync();
using (var cmd = new SqlCommand
{
CommandType = commandType,
CommandTimeout = taskTimeoutInSeconds, // in secs
CommandText = "JerrySimulateSlowSp"
})
{
cmd.Parameters.AddRange(parameters.ToArray());
cmd.Connection = connection;
using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
{
return mapper.Map<IDataReader, IEnumerable<T>>(reader);
}
}
}
});
return results;
//cmd.Connection = null;
}
catch (SqlException ex) when (ex.Number == -2) // -2 is a sql timeout
{
throw new ThunderTimeoutException(Constants.HttpResponseTimeoutSql);
}
catch (TimeoutRejectedException)
{
throw new ThunderTimeoutException(Constants.HttpResponseTimeoutTask);
}
Polly的超时策略支持两种操作:
- 乐观:装饰方法可以与
CancellationToken
合作。 - 悲观:装饰方法不能与
CancellationToken
合作
幸运的是,ExecuteReaderAsync
支持CancellationToken
,因此我们可以在这里使用乐观超时策略。诀窍在于你可以使用不同的ExecuteAsync
.ExecuteAsync(async ct =>
{
...
var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection, ct);
...
}, CancellationToken.None);
在这种情况下,ExecuteReaderAsync
将使用超时的CancellationToken
。如果你有另一个CancellationToken
(例如允许基于用户交互的取消),那么你可以通过传递该令牌而不是CancellationToken.None
来将其与超时的一个相结合。
.ExecuteAsync(async combinedToken =>
{
...
var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection, combinedToken);
...
}, userCancellationToken);
旁注:请优先选择PolicyWrap
而不是WrapAsync
var combinedPolicy = PolicyWrap.WrapAsync(retryPolicy, timeoutPerTry);
相关SO主题:1,2,3