Policy.TimeoutAsync Полли не работает с PolicyWrap в асинхронном контексте

Это полностью рабочий пример (скопируйте / вставьте его и поиграйте, просто получите Polly Nuget)

У меня есть следующий код консольного приложения, который отправляет запрос POST в изолированную программную среду HTTP-клиента на " http://ptsv2.com/t/v98pb-1521637251/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 со значением по умолчанию TimeoutStrategy.Optimistic работает по тайм-ауту CancellationToken поэтому делегаты, которых вы выполняете, должны ответить на совместную отмену. Смотрите 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.
);

Полные асинхронные выполнения по умолчанию не продолжаются в захваченном контексте синхронизации (они выполняются с .ConfigureAwait(false)), так что это также может быть сокращено до:

var response = await Policies.PolicyWrap.ExecuteAsync(
    ct => _httpClient.PostAsync(/* uri */, new StringContent(request), ct), 
    CancellationToken.None
);
Другие вопросы по тегам