Polly 的 Policy.TimeoutAsync 不适用于异步上下文中的 PolicyWrap
Posted
技术标签:
【中文标题】Polly 的 Policy.TimeoutAsync 不适用于异步上下文中的 PolicyWrap【英文标题】:Polly's Policy.TimeoutAsync does not work with PolicyWrap in an async context 【发布时间】:2018-03-21 17:20:31 【问题描述】:这是一个完全可用的示例(复制/粘贴它并玩弄,只需获取 Polly Nuget)
我有以下控制台应用程序代码,它向“http://ptsv2.com/t/v98pb-1521637251/post”上的 HTTP 客户端沙箱发出 POST 请求(您可以访问此链接“http://ptsv2.com/t/v98pb-1521637251”以查看配置或将自己设置为“厕所”):
class Program
private static readonly HttpClient _httpClient = new HttpClient()
; //WHY? BECAUSE - https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
static void Main(string[] args)
_httpClient.BaseAddress = new Uri(@"http://ptsv2.com/t/v98pb-1521637251/post");
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));
var result = ExecuteAsync("Test").Result;
Console.WriteLine(result);
Console.ReadLine();
private static async Task<string> ExecuteAsync(string request)
var response = await Policies.PolicyWrap.ExecuteAsync(async () => await _httpClient.PostAsync("", new StringContent(request)).ConfigureAwait(false));
if (!response.IsSuccessStatusCode)
return "Unsuccessful";
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Http 客户端等待 4 秒,然后返回响应。
我已经设置了这样的策略(超时策略设置为等待 1 秒响应):
public static class Policies
public static TimeoutPolicy<HttpResponseMessage> TimeoutPolicy
get
return Policy.TimeoutAsync<HttpResponseMessage>(1, onTimeoutAsync: (context, timeSpan, task) =>
Console.WriteLine("Timeout delegate fired after " + timeSpan.TotalMilliseconds);
return Task.CompletedTask;
);
public static RetryPolicy<HttpResponseMessage> RetryPolicy
get
return Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.Or<TimeoutRejectedException>()
.RetryAsync(3, onRetryAsync: (delegateResult, i) =>
Console.WriteLine("Retry delegate fired for time No. " + i);
return Task.CompletedTask;
);
public static FallbackPolicy<HttpResponseMessage> FallbackPolicy
get
return Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.Or<TimeoutRejectedException>()
.FallbackAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError), onFallbackAsync: (delegateResult, context) =>
Console.WriteLine("Fallback delegate fired");
return Task.CompletedTask;
);
public static PolicyWrap<HttpResponseMessage> PolicyWrap
get
return Policy.WrapAsync(FallbackPolicy, RetryPolicy, TimeoutPolicy);
但是,onTimeoutAsync 委托根本没有被命中,控制台打印出以下内容:
Retry delegate fired for time No. 1 //Hit after 4 seconds
Retry delegate fired for time No. 2 //Hit after 4 more seconds
Retry delegate fired for time No. 3 //Hit after 4 more seconds
Fallback delegate fired
Unsuccessful
它包含在 PolicyWrap 中并且是异步的,我不知道为什么它没有被命中。 任何信息都非常感谢。
【问题讨论】:
【参考方案1】:Polly Timeout policy 与默认 TimeoutStrategy.Optimistic
通过超时 CancellationToken
运行,因此您执行的代表必须响应 co-operative cancellation。有关详细信息,请参阅Polly Timeout wiki。
将您的执行行更改为以下内容应该可以使超时工作:
var response = await Policies.PolicyWrap.ExecuteAsync(
async ct => await _httpClient.PostAsync(/* uri */, new StringContent(request), ct).ConfigureAwait(false),
CancellationToken.None // CancellationToken.None here indicates you have no independent cancellation control you wish to add to the cancellation provided by TimeoutPolicy. You can also pass in your own independent CancellationToken.
);
Polly 异步执行默认为 do not continue on captured synchronization context(它们使用 .ConfigureAwait(false)
执行),因此也可以缩短为:
var response = await Policies.PolicyWrap.ExecuteAsync(
ct => _httpClient.PostAsync(/* uri */, new StringContent(request), ct),
CancellationToken.None
);
【讨论】:
谢谢@mountaintraveller,您提供的解决方案确实很有魅力!您是否有机会知道在许多线程命中策略的情况下缺少 ConfigureAwait(bool continueOnCapturedContext) 是否不会导致死锁问题 -> _httpClient? P.S.刚刚检查了许多同时命中策略的任务,并且不需要 ConfigureAwait(false)。 所有 Polly 策略都是完全线程安全的。当同步上下文是 UI 上下文或其他单线程或不可重入上下文时,当您使用await
而没有 .ConfigureAwait(false)
时,异步执行中可能会发生死锁。参考文献:blog.stephencleary.com/2012/07/dont-block-on-async-code.html; blogs.msdn.microsoft.com/pfxteam/2011/01/13/…
再次感谢您的意见,我们会好好阅读它们。
Polly async 在内部到处都有.ConfigureAwait(...)
:例如github.com/App-vNext/Polly/blob/… 用于TimeoutAsync。 Polly 默认为.ConfigureAwait(false)
,例如这里显示的github.com/App-vNext/Polly/blob/…。所以.ConfigureAwait(false)
不需要在 执行的委托中。是否需要 .ConfigureAwait(false)
after .ExecuteAsync(...)
dpds on context ExecuteAsync(...)
被调用。以上是关于Polly 的 Policy.TimeoutAsync 不适用于异步上下文中的 PolicyWrap的主要内容,如果未能解决你的问题,请参考以下文章