Polly 不超时
Polly does not timeout
我试图让 Polly 在超时 3 秒后以及返回某些 http 代码时重试。但是,直到 100 秒后 HttpClient 超时才超时。
这是我的代码:
private static Polly.Wrap.AsyncPolicyWrap<HttpResponseMessage> GetPolicy()
{
var timeoutPolicy = Policy.TimeoutAsync(3, Polly.Timeout.TimeoutStrategy.Optimistic);
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == HttpStatusCode.TooManyRequests ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3));
var policy = retryPolicy.WrapAsync(timeoutPolicy);
return policy;
}
更新
根据要求,这是我使用政策的代码。
var pollyResponse = await GetPolicy().ExecuteAndCaptureAsync(() =>
httpClient.SendAsync(GetMessage(HttpMethod.Delete, endpoint))
);
以及生成 HttpRequestMessage 的辅助方法:
private HttpRequestMessage GetMessage<T>(HttpMethod method, string endpoint, T content)
{
var message = new HttpRequestMessage
{
Method = method,
RequestUri = new Uri(endpoint),
Headers = {
{ "MyCustomHeader", _value },
{ HttpRequestHeader.Accept.ToString(), "application/json" }
}
};
if (content != null)
{
var contentAsString = JsonSerializer.Serialize(content);
message.Content = new StringContent(contentAsString);
}
return message;
}
首先给大家分享一下你的修改版GetPolicy
:
private static IAsyncPolicy<HttpResponseMessage> GetStrategy()
{
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(3, TimeoutStrategy.Optimistic,
onTimeoutAsync: (_, __, ___, ____) =>
{
Console.WriteLine("Timeout has occurred");
return Task.CompletedTask;
});
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == (HttpStatusCode)429 ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3),
onRetryAsync: (_, __, ___) =>
{
Console.WriteLine("Retry will fire soon");
return Task.CompletedTask;
});
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
- 我更改了 return 类型,因为从消费者的角度来看,
PolicyWrap
只是一个实现细节
- 如果你不想使用接口,你也可以使用
AsyncPolicy<T>
抽象 class 作为 return 类型 (IAsyncPolicy<T>
)
- 我添加了一些调试日志记录(
onTimeoutAsync
、onRetryAsync
)以便能够观察在 时触发了哪个策略
- 我在
retryPolicy
上添加了一个 Or<TimeoutRejectedException>()
构建器函数调用,以确保在超时的情况下会触发重试
- 请注意,超时策略会抛出 its own exception
- 我也把你的
retryPolicy.WrapAsync
改成了 PolicyWrap because with that the escalation chain 更明确
- 最左边的策略是最外面的
- 最右策略是最内
- 我还更改了
timeoutPolicy
(.TimeoutAsync < HttpResponseMessage > ) 以与重试策略保持一致(它们都包装了一个可能 return一个Task<HttpResponseMessage>
)
为了能够测试我们的弹性策略(注意命名)我创建了以下辅助方法:
private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}");
}
现在,让我们调用网站:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async () => await CallOverloadedAPI());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
输出:
Finished
等等,什么???
问题是 none 的政策已被触发。
为什么?
因为 5 秒后我们收到了 200 的响应。
但是,我们已经设置了超时,对吧?
是和不是。 :) 即使我们定义了超时策略,我们还没有真正将其连接到 HttpClient
那么,我该如何连接?
好吧,通过 CancellationToken
因此,在超时策略的情况下,如果正在使用 CancellationToken
,则它可以调用其 Cancel
方法来向 HttpClient 指示超时事实。并且 HttpClient 将取消挂起的请求。
请注意,因为我们正在使用 TimeoutPolicy,所以异常将是 TimeoutRejectedException
,而不是 OperationCanceledException
。
所以,让我们修改代码以接受 CancellationToken
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200, CancellationToken token = default)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}", token);
}
我们也必须调整使用方面:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async (ct) => await CallOverloadedAPI(token: ct), CancellationToken.None);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
现在输出将如下所示:
Timeout has occurred
Retry will fire soon
Timeout has occurred
Retry will fire soon
Timeout has occurred
Retry will fire soon
Timeout has occurred
The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.
最后一行是 TimeoutRejectedException
的 Message
。
请注意,如果我们从 retryPolicy
构建器中删除 Or<TimeoutRejectedException>()
调用,则输出将如下所示:
Timeout has occurred
The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.
所以,现在会触发重试。不会有升级。
为了完整起见,这里是完整的源代码:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async (ct) => await CallOverloadedAPI(token: ct), CancellationToken.None);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
private static AsyncPolicy<HttpResponseMessage> GetStrategy()
{
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(3, TimeoutStrategy.Optimistic,
onTimeoutAsync: (_, __, ___, ____) =>
{
Console.WriteLine("Timeout has occurred");
return Task.CompletedTask;
});
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == (HttpStatusCode)429 ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3),
onRetryAsync: (_, __, ___) =>
{
Console.WriteLine("Retry will fire soon");
return Task.CompletedTask;
});
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200, CancellationToken token = default)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}", token);
}
我试图让 Polly 在超时 3 秒后以及返回某些 http 代码时重试。但是,直到 100 秒后 HttpClient 超时才超时。
这是我的代码:
private static Polly.Wrap.AsyncPolicyWrap<HttpResponseMessage> GetPolicy()
{
var timeoutPolicy = Policy.TimeoutAsync(3, Polly.Timeout.TimeoutStrategy.Optimistic);
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == HttpStatusCode.TooManyRequests ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3));
var policy = retryPolicy.WrapAsync(timeoutPolicy);
return policy;
}
更新
根据要求,这是我使用政策的代码。
var pollyResponse = await GetPolicy().ExecuteAndCaptureAsync(() =>
httpClient.SendAsync(GetMessage(HttpMethod.Delete, endpoint))
);
以及生成 HttpRequestMessage 的辅助方法:
private HttpRequestMessage GetMessage<T>(HttpMethod method, string endpoint, T content)
{
var message = new HttpRequestMessage
{
Method = method,
RequestUri = new Uri(endpoint),
Headers = {
{ "MyCustomHeader", _value },
{ HttpRequestHeader.Accept.ToString(), "application/json" }
}
};
if (content != null)
{
var contentAsString = JsonSerializer.Serialize(content);
message.Content = new StringContent(contentAsString);
}
return message;
}
首先给大家分享一下你的修改版GetPolicy
:
private static IAsyncPolicy<HttpResponseMessage> GetStrategy()
{
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(3, TimeoutStrategy.Optimistic,
onTimeoutAsync: (_, __, ___, ____) =>
{
Console.WriteLine("Timeout has occurred");
return Task.CompletedTask;
});
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == (HttpStatusCode)429 ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3),
onRetryAsync: (_, __, ___) =>
{
Console.WriteLine("Retry will fire soon");
return Task.CompletedTask;
});
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
- 我更改了 return 类型,因为从消费者的角度来看,
PolicyWrap
只是一个实现细节- 如果你不想使用接口,你也可以使用
AsyncPolicy<T>
抽象 class 作为 return 类型 (IAsyncPolicy<T>
)
- 如果你不想使用接口,你也可以使用
- 我添加了一些调试日志记录(
onTimeoutAsync
、onRetryAsync
)以便能够观察在 时触发了哪个策略
- 我在
retryPolicy
上添加了一个Or<TimeoutRejectedException>()
构建器函数调用,以确保在超时的情况下会触发重试- 请注意,超时策略会抛出 its own exception
- 我也把你的
retryPolicy.WrapAsync
改成了 PolicyWrap because with that the escalation chain 更明确- 最左边的策略是最外面的
- 最右策略是最内
- 我还更改了
timeoutPolicy
(.TimeoutAsync < HttpResponseMessage > ) 以与重试策略保持一致(它们都包装了一个可能 return一个Task<HttpResponseMessage>
)
为了能够测试我们的弹性策略(注意命名)我创建了以下辅助方法:
private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}");
}
现在,让我们调用网站:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async () => await CallOverloadedAPI());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
输出:
Finished
等等,什么??? 问题是 none 的政策已被触发。
为什么? 因为 5 秒后我们收到了 200 的响应。
但是,我们已经设置了超时,对吧? 是和不是。 :) 即使我们定义了超时策略,我们还没有真正将其连接到 HttpClient
那么,我该如何连接?
好吧,通过 CancellationToken
因此,在超时策略的情况下,如果正在使用 CancellationToken
,则它可以调用其 Cancel
方法来向 HttpClient 指示超时事实。并且 HttpClient 将取消挂起的请求。
请注意,因为我们正在使用 TimeoutPolicy,所以异常将是 TimeoutRejectedException
,而不是 OperationCanceledException
。
所以,让我们修改代码以接受 CancellationToken
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200, CancellationToken token = default)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}", token);
}
我们也必须调整使用方面:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async (ct) => await CallOverloadedAPI(token: ct), CancellationToken.None);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
现在输出将如下所示:
Timeout has occurred
Retry will fire soon
Timeout has occurred
Retry will fire soon
Timeout has occurred
Retry will fire soon
Timeout has occurred
The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.
最后一行是 TimeoutRejectedException
的 Message
。
请注意,如果我们从 retryPolicy
构建器中删除 Or<TimeoutRejectedException>()
调用,则输出将如下所示:
Timeout has occurred
The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.
所以,现在会触发重试。不会有升级。
为了完整起见,这里是完整的源代码:
public static async Task Main()
{
HttpResponseMessage response;
try
{
response = await GetStrategy().ExecuteAsync(async (ct) => await CallOverloadedAPI(token: ct), CancellationToken.None);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Environment.Exit(-1);
}
Console.WriteLine("Finished");
}
private static AsyncPolicy<HttpResponseMessage> GetStrategy()
{
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(3, TimeoutStrategy.Optimistic,
onTimeoutAsync: (_, __, ___, ____) =>
{
Console.WriteLine("Timeout has occurred");
return Task.CompletedTask;
});
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r =>
r.StatusCode == (HttpStatusCode)429 ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode == HttpStatusCode.Forbidden)
.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(3),
onRetryAsync: (_, __, ___) =>
{
Console.WriteLine("Retry will fire soon");
return Task.CompletedTask;
});
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
private static HttpClient client = new HttpClient();
public static async Task<HttpResponseMessage> CallOverloadedAPI(int responseDelay = 5000, int responseCode = 200, CancellationToken token = default)
{
return await client.GetAsync($"http://httpstat.us/{responseCode}?sleep={responseDelay}", token);
}