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的主要内容,如果未能解决你的问题,请参考以下文章

Polly的多种弹性策略介绍和简单使用

弹性和瞬态故障处理库Polly

Polly

如何为每个 url 使用 Polly 和 IHttpClientFactory

熔断降级(Polly)

熔断 降级(polly)