【问题标题】:Polly's Policy.TimeoutAsync does not work with PolicyWrap in an async contextPolly 的 Policy.TimeoutAsync 不适用于异步上下文中的 PolicyWrap
【发布时间】: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 中并且是异步的,我不知道为什么它没有被命中。 任何信息都非常感谢。

【问题讨论】:

    标签: asynchronous timeout dotnet-httpclient policy polly


    【解决方案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.htmlblogs.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(...) 被调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-22
    • 1970-01-01
    • 2017-08-24
    • 1970-01-01
    • 2014-07-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多