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

Roy*_*mir 4 c# dotnet-httpclient .net-core polly retry-logic

我将 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();
}
Run Code Online (Sandbox Code Playgroud)

哪里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}  ");
            });
}
Run Code Online (Sandbox Code Playgroud)

输出是:

_ PollyAttempt retryCount:1
_ PollyAttempt retryCount:2
_ PollyAttempt retryCount:3
异常 : (TimeoutException) 通过 TimeoutPolicy 异步执行的委托未在超时内完成。

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

问题:

我怎样才能捕获最后的异常?是否有任何内置机制可以让我知道 Polly 失败了?

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

Pet*_*ala 5

让我们从一个根本不使用 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();
}
Run Code Online (Sandbox Code Playgroud)
  • 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();
}
Run Code Online (Sandbox Code Playgroud)
  • 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();
}
Run Code Online (Sandbox Code Playgroud)
  • 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();
}
Run Code Online (Sandbox Code Playgroud)
  • 14 秒后(3+1 个 2 秒长请求和 3 个 2 秒长惩罚),重试策略抛出原始异常,即 innerPolicy 的TimeoutRejectedException.
    • 该异常的内部是 HttpClient 的TaskCanceledException.

更新:抓住评论的本质

我知道所有尝试都失败的点在哪里?

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

我应该检查异常以查看它是 timeoutException 吗?

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

我是否需要捕获第一个 TimeoutRejectedException 异常才能识别退休失败?

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

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

换句话说,TimeoutRejectedException如果客户端在给定时间段内没有收到下游系统的答复,则会抛出该异常。HttpRequestException但如果存在一些网络问题,它也可能会抛出。

  • 如果重试配置为处理该问题,那么您可以确定,如果抛出该问题,则所有重试尝试都会失败。
  • 如果未配置,则无需重试,它将抛出HttpRequestException.