指定放弃/失败动作

本文关键字:失败 放弃 | 更新日期: 2023-09-27 18:15:55

我正在使用Polly发出HTTP请求,如果请求失败,则重试5次。

当5次尝试失败并且策略放弃时,是否可以指定一个操作?

在下面的代码中;当我们失败了5次,我知道用户没有网络,所以我想显示一个消息框,说"应用程序需要网络"。我可以使用计数器来计数5次失败,但使用Polly方法会更好。

var policy = Polly.Policy.Handle<Exception>().WaitAndRetryAsync(
   5,
   retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
   (ex, span) =>
   {
       Mvx.Trace("Retried because of {0}", ex);
   }
);
await policy.ExecuteAsync(() => MakeRequestEx<T>(requestUrl, verb, accept, headers, baseAddress)).ConfigureAwait(false);

指定放弃/失败动作

是的,您可以使用ExecuteAndCapture

var policyResult = await policy.ExecuteAndCaptureAsync(
    () => MakeRequestEx<T>(requestUrl, verb, accept, headers, baseAddress)
).ConfigureAwait(false);

检查policyResultOutcome是否呼叫失败,并显示提示信息。

你的方法有几个问题,让我试着描述一下

不是所有的运算都是幂等的

基于您的共享代码,我假设您的MakeRequestEx函数是通用的,足以发出GETPOST动词的请求。如果你盲目地对任何类型的请求应用重试逻辑,那么你可能会得到不想要的重复或不可逆的副作用。

这就是为什么为了使用重试逻辑,您应该满足以下条件组:

  • 潜在引入的可观察影响是可接受的
  • 手术可重做,无不可逆副作用
  • 与承诺的可靠性相比,引入的复杂性可以忽略不计

大多数情况下,GET的操作是以幂等方式写的,但其他(CRU)操作则不是这样。如果您想使用您的策略+ MakeRequestEx,请确保您的下游系统支持所有动词的请求重复数据删除。

不是所有的错误都是短暂的

如果抛出异常,您希望根据共享代码重试。这种方法的问题是,并不是所有的异常都代表一个暂时的问题。

如果你重新执行一个操作,比如10/0,那么无论你想重试多少次,你总是会收到一个DivideByZeroException。http请求也是如此。例如,如果url无效,那么您将收到UrlFormatException。如果你重新做N次,它仍然是一个UrlFormatException,因为它不是一个短暂的失败。

大多数情况下,如果您收到HttpRequestException或响应状态码是这些:408,429或5XX之一,则重新执行http请求就足够了。


计数重试次数

当我们失败了5次时,我知道用户没有互联网,所以我想显示一个消息框,说'应用程序需要互联网'

重试策略的ExecuteExecuteAsync方法的工作方式是,如果在N次重试后操作不能成功完成,则抛出最后一次尝试的异常。

所以,如果你用try - catch块包装你的ExecuteAsync,那么你可以确保当执行流到catch块时,所有的重试都失败了。

try
{
   await policy.ExecuteAsync(() => MakeRequestEx<T(...));
}
catch
{
   MessageBox.Show("Operation failed after 6 attempts");
}

MessageBox的消息中,我已经写了6次尝试,因为您已经将策略配置为5次重试,但是还有第0次尝试。

如果你不想使用try - catch块,那么你可以使用ExecuteAndCapture/ExecuteAndCaptureAsync方法。这些方法不会抛出异常,如果所有重试尝试失败,而是填充PolicyResultFinalException属性,并将Outcome属性设置为Failure

var retryResult = await policy.ExecuteAndCaptureAsync(() => MakeRequestEx<T(...));
if (retryResult.Outcome == Outcome.Failure)
    MessageBox.Show("Operation failed after 6 attempts");

如果您想知道发出了多少重试尝试,那么您需要使用Context对象。ExecuteAsync/ExecuteAndCaptureAsync是在AsyncPolicy基类上定义的,所以它们不是特定于重试策略的,这就是为什么它们不公开发出的重试尝试。

如果你像这样定义一些辅助方法:

public static class ContextExtensions
{
    private static readonly string key = "RetryCount";
    public static void IncreaseRetryCount(this Context context)
    {
        var retryCount = GetRetryCount(context);
        context[key] = ++retryCount;    
    }
    public static int GetRetryCount(this Context context)
    {
        context.TryGetValue(key, out object count);
        return count != null ? (int)count : 0;
    }
}

则可以调用

  • 每次重试时的IncreaseRetryCount
  • 策略执行后的GetRetryCount
var policy = Polly.Policy
    .Handle<Exception>()
    .WaitAndRetryAsync(5,
       retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
       (ex, _, ctx) =>
       {
            Mvx.Trace("Retried because of {0}", ex);
            ctx.IncreaseRetryCount();
       });
var result = await policy.ExecuteAndCaptureAsync(() => MakeRequestEx<T(...));
var outcome = result.Outcome == OutcomeType.Successful ? "completed" : "failed";
MessageBox.Show($"Operation has {outcome} after the initial attempt + {result.Context.GetRetryCount()} retry attempts");