在 .Net Core 中捕获 Polly 中的最后一个异常?

Catching the last exception in Polly in .Net Core?

我正在使用 Polly (Microsoft.Extensions.Http.Polly) 和 .net core 以及此配置(带有无效的 URL ,用于测试):

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(2); // Timeout for an individual try
 

    collection.AddHttpClient<INetworkService, NetworkService>(url=>
             {
                 url.BaseAddress = new Uri("http://www.google.com:81"); //test bad url
             })
             .AddPolicyHandler(GetRetryPolicy()) 
             .AddPolicyHandler(timeoutPolicy); ;

    _serviceProvider = collection.BuildServiceProvider();
}

其中 GetRetryPolicy 是:

private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode != HttpStatusCode.OK)
        .Or<TimeoutRejectedException>()
        .Or<TaskCanceledException>()
        .Or<OperationCanceledException>()
        .WaitAndRetryAsync(3, retryAttempt =>
        {
        return  TimeSpan.FromSeconds(2);
        }, 
        onRetry: (response, delay, retryCount, context) =>
            {
              Console.WriteLine($"______PollyAttempt_____ retryCount:{retryCount}  ");
            });
}

输出为:

_PollyAttempt retryCount:1
_PollyAttempt retryCount:2
_PollyAttempt retryCount:3
Exception : (TimeoutException) The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.

我想在上次尝试失败后发送电子邮件。

问题:

如何捕获 final 异常?是否有任何内置机制让我知道 Polly 失败了?

(我目前的工作代码:https://pastebin.pl/view/a2566d51

让我们从根本不使用 Polly 的简单设置开始:

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    collection.AddHttpClient<INetworkService, NetworkService>(sonol =>
    {
        sonol.BaseAddress = new Uri("http://www.google.com:81");
    });

    _serviceProvider = collection.BuildServiceProvider();
}
  • 100 秒后(HttpClient 的默认超时),它将失败并显示 TaskCanceledException。换句话说,HttpClient 取消了请求,因为它没有收到任何响应。

现在让我们稍微调整一下 HttpClient 设置:

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    collection.AddHttpClient<INetworkService, NetworkService>(sonol =>
    {
        sonol.Timeout = TimeSpan.FromSeconds(3); // << NEW CODE
        sonol.BaseAddress = new Uri("http://www.google.com:81");
    });

    _serviceProvider = collection.BuildServiceProvider();
}
  • 3 秒后,HttpClient 取消请求并抛出 TaskCanceledException

现在,注释掉这个超时设置,让我们连接超时策略:

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(2);

    collection.AddHttpClient<INetworkService, NetworkService>(sonol =>
    {
        //sonol.Timeout = TimeSpan.FromSeconds(3);
        sonol.BaseAddress = new Uri("http://www.google.com:81");
    })
    .AddPolicyHandler(timeoutPolicy); // << NEW CODE

    _serviceProvider = collection.BuildServiceProvider();
}
  • 2 秒后 Polly 的 TimeoutPolicy 取消请求并抛出 TimeoutRejectedException
    • 它的InnerException是原来的TaskCanceledException

最后让我们添加重试策略:

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(2);

    collection.AddHttpClient<INetworkService, NetworkService>(sonol =>
    {
        //sonol.Timeout = TimeSpan.FromSeconds(3);
        sonol.BaseAddress = new Uri("http://www.google.com:81");
    })
    .AddPolicyHandler(Policy.WrapAsync(GetRetryPolicy(), timeoutPolicy)); // << NEW CODE
    //.AddPolicyHandler(timeoutPolicy);

    _serviceProvider = collection.BuildServiceProvider();
}
  • 14 秒后(3+1 个 2 秒长的请求和 3 个 2 秒长的惩罚)重试策略抛出原始异常,即 innerPolicy 的 TimeoutRejectedException
    • 那个异常的 Inner 是 HttpClient 的 TaskCanceledException

更新:捕捉评论的精华

Where is the point where I know that all attempts have failed?

当你的 Polly decorated HttpClient 抛出 TimeoutRejectedException 时,你可以确定所有尝试都失败了。因此,您应该将 GetAsync 与 try-catch 包装在类型化的客户端中。

Should I check the exception to see that it's timeoutException?

如果格式不正确url,它将抛出不同的异常。因此,如果您发现 TimeoutRejectedException,则意味着下游不可用或过载。

Do I need to catch first TimeoutRejectedException exception in order to recognize retires have failed?

从消费者的角度来看,只有一个例外。重试策略会抛出

  • 当它用完重试次数时
  • 或未配置为处理它时。
    • 所有未通过 Handle<>Or<> 调用明确列出的异常都被视为未处理。这意味着无需重试,策略就会抛出该错误。

换句话说,如果客户端在给定时间段内未收到来自下游系统的应答,将抛出 TimeoutRejectedException。但如果存在网络问题,它也可能会抛出 HttpRequestException

  • 如果重试配置为处理该问题,那么您可以确定,如果它被抛出,那么所有重试尝试都会失败。
  • 如果未配置,则在不重试的情况下将抛出 HttpRequestException