确保 Polly 策略至少运行一次

Ensure Polly Policy Runs at Least Once

所以,我正在编写一些使用 Polly 获取锁的重试逻辑。总超时值将由 API 调用者提供。我知道我可以在整体超时中包装策略。但是,如果提供的超时值太低,有什么方法可以确保该策略至少执行一次?

显然我可以在执行策略之前单独调用委托,但我只是想知道是否有一种方法可以在 Polly 中表达这个要求。

var result = Policy.Timeout(timeoutFromApiCaller)
                    .Wrap(Policy.HandleResult(false)
                                .WaitAndRetryForever(_ => TimeSpan.FromMilliseconds(500))
                    .Execute(() => this.TryEnterLock());

如果 timeoutFromApiCaller1 tick 并且很可能需要比这更长的时间才能达到超时策略,那么委托将不会被调用(该策略将超时并抛出 TimeoutRejectedException).

我希望发生的事情可以表示为:

var result = this.TryEnterLock();

if (!result)
{
    result = Policy.Timeout(timeoutFromApiCaller)
                   .Wrap(Policy.HandleResult(false)
                               .WaitAndRetryForever(_ => TimeSpan.FromMilliseconds(500))
                   .Execute(() => this.TryEnterLock());
}

不过要是能用纯Polly的方式表达就好了...

老实说,我不明白 1 tick 是什么意思?是纳秒还是大于纳秒?您的全局超时应大于本地超时。

但据我所知,您未指定本地TryEnterLock 应该收到一个 TimeSpan,以免无限期地阻塞调用者。如果您查看内置的同步原语,它们中的大多数都提供这样的功能:Monitor.TryEnter, SpinLock.TryEnter, WaitHandle.WaitOne,等等

所以,总结一下:

var timeoutPolicy = Policy.Timeout(TimeSpan.FromMilliseconds(1000));
var retryPolicy = Policy.HandleResult(false)
       .WaitAndRetryForever(_ => TimeSpan.FromMilliseconds(500));
var resilientStrategy = Policy.Wrap(timeoutPolicy, retryPolicy);    

var result = resilientStrategy.Execute(() => this.TryEnterLock(TimeSpan.FromMilliseconds(100))); 

超时和延迟值应根据您的业务需要进行调整。我强烈建议您记录全局超时 (onTimeout / onTimeoutAsync) 触发和重试 (onRetry / onRetryAsync) 的时间,以便能够微调/校准这些值。


编辑:基于此 post

的评论

事实证明,timeoutFromApiCaller 无法控制,因此它可以任意小。 (在给定的示例中,它只是几纳秒,目的是强调这个问题。)因此,为了至少有一个调用保证,我们必须使用 Fallback policy.

我们应该将其称为最后一个操作,而不是预先手动调用策略外的TryEnterLock,以满足要求。因为策略使用升级,这就是为什么每当 inner 失败时它就会将问题委托给下一个 outer 策略。

因此,如果提供的超时时间太短以至于操作要到那个时间才能完成,那么它将抛出 TimeoutRejectedException。有了回退,我们可以处理这个问题,并且可以再次 执行操作,但现在没有任何超时限制。这将为我们提供所需的至少一项保证。

var atLeastOnce = Policy.Handle<TimeoutRejectedException>
    .Fallback((ct) => this.TryEnterLock());
var globalTimeout = Policy.Timeout(TimeSpan.FromMilliseconds(1000));
var foreverRetry = Policy.HandleResult(false)
       .WaitAndRetryForever(_ => TimeSpan.FromMilliseconds(500));

var resilientStrategy = Policy.Wrap(atLeastOnce, globalTimeout, foreverRetry);    
var result = resilientStrategy.Execute(() => this.TryEnterLock());