Политика Polly Retry с функцией не ожидает результата - PullRequest
0 голосов
/ 12 июня 2018

Я пытаюсь преобразовать свою существующую функцию в политику Polly Retry

public static T Execute<T>(Func<T> getTask) where T : Task
{
    var retryCount = 3;
    while (retryCount-- > 0)
    {
        try
        {
            getTask().Wait();
            return getTask();
        } catch(Exception ex){
            // handle retry
        }
    }
}

Преобразована в эту

public static T Execute<T>(Func<T> func) where T : Task
{
    var task = func();
    Policy.Handle<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(
            3,
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(5, retryAttempt)),
            (exception, timeSpan, retryCount, context) =>
            {
                //do some logging
            })
        .ExecuteAsync(func).Wait();
    return task;
}

, и тестовый код

var retryCount = 0;
var res = HttpRetryWrapper.Execute(() => Task.Factory.StartNew<string>(function: () =>
{
    if (++retryCount == 3)
    {
        return "fake";
    }
    throw new TimeoutException();
}));

, когдаЯ утверждаю значение res, я не получаю правильный результат.Отладка приводит меня к тому, что Execution не ожидает результатов должным образом.

Количество вызовов на test function является правильным.Однако запись в журнал испорчена, и окончательный результат не дает результата fake

Ответы [ 2 ]

0 голосов
/ 13 июня 2018

Для вспомогательного метода для асинхронных выполнений с помощью жестко заданной политики Полли, где выполнения асинхронно возвращают тип TResult, через Task<TResult> вы можете принять:

public static Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func) 
{
    return Policy.Handle<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(
            3,
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(5, retryAttempt)),
            (exception, timeSpan, retryCount, context) =>
            {
                //do some logging
            })
        .ExecuteAsync<TResult>(func); // This is an async-await-eliding contraction of: .ExecuteAsync<TResult>(async () => await func());
    }

(учитываяиспользуемая политика каждый раз одна и та же, вы можете также сохранить политику в статическом поле и создать ее только один раз.)

Примечание: Это (намеренно) не соответствуетконтракт, указанный в вашем комментарии к вашему первоначальному вопросу, как неразрывный:

public static T Execute<T>(Func<T> func) where T : Task

Предложение where T : Task изначально выглядит привлекательным для асинхронного или асинхронного - типа метода, предназначенного для работы слибо Task, либо Task<T>.Джон Скит объясняет здесь и здесь , почему он не работает с асинхронной синхронизацией.Предложенная вами сигнатура вспомогательного метода сама по себе не является асинхронной:

public static T Execute<T>(Func<T> func) where T : Task

Однако, введение .ExecuteAsync(async () => await func()); в вашем примере кода вызывает аналогичную проблему.Перегрузка Polly .ExecuteAsync(...) именно для того, чтобы хорошо играть с async / await, существует в двух основных формах:

(1) Task ExecuteAsync(Func<Task> func)

(2) Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func)

Компилятор должен выбрать один или другой во время компиляции : он не может (в вашем примере кода) скомпилировать его, чтобы он был либо (1) или (2) во время выполнения .Поскольку он знает только T : Task, он выбирает (1), что возвращает Task.Отсюда и ошибка, которую вы видите в своем комментарии к ответу @ JamesFaix: Cannot implicitly convert type Task to T.

Если вам нужны вспомогательные шаблоны этой формы, которые вызывающие абоненты могут использовать для Task или Task<TResult> - повторных вызовов, выпридется объявить оба:

class HttpRetryWrapper
{
    private static policy = Policy.Handle<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(
            3,
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(5, retryAttempt)),
            (exception, timeSpan, retryCount, context) =>
            {
                //do some logging
            });

    public static Task ExecuteAsync(Func<Task> func) 
    {
        return policy.ExecuteAsync(func);
    }

    public static Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func) 
    {
        return policy.ExecuteAsync<TResult>(func);
    }
}

Наконец, если это для упаковки вызовов, размещенных через HttpClient, рекомендуемый шаблон - поместить политики Полли в DelegatingHandler, как описано в @Ответ Мухаммеда Рехана Сайда здесь .ASP.NET Core 2.1 поддерживает краткое объявление для создания таких DelegatingHandler s с использованием IHttpClientFactory .

0 голосов
/ 12 июня 2018

Я думаю, что вам нужно:

public static T Execute<T>(Func<T> func) where T : Task
{
    return Policy.Handle<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(
            3,
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(5, retryAttempt)),
            (exception, timeSpan, retryCount, context) =>
            {
                //do some logging
            })
        .ExecuteAsync(func).Wait();
}

В вашем примере кода вы вызывали func один раз и возвращали его, а затем между определением и вызовом политики, но не возвращали результатссылаясь на эту политику.

Если вы хотите избежать Wait Я думаю, вы могли бы также сделать

public static T Execute<T>(Func<T> func) where T : Task
{
    return Policy.Handle<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(
            3,
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(5, retryAttempt)),
            (exception, timeSpan, retryCount, context) =>
            {
                //do some logging
            })
        .ExecuteAsync(async () => await func());
}
...