使用Function的Polly重试策略不等待结果

时间:2018-06-12 12:46:44

标签: c# policy polly retrypolicy

我正在尝试将现有功能转换为Polly重试政策

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 :(得分:2)

对于通过硬编码Polly策略执行异步执行的辅助方法,执行异步返回类型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最初看起来很有吸引力,因为方法可以与TaskTask<T>一起使用。 Jon Skeet解释herehere为什么它不能与异步一起使用。您建议的帮助方法签名本身不是异步的:

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

但是,在示例代码中引入.ExecuteAsync(async () => await func());会导致类似的问题。为了与.ExecuteAsync(...) / async很好地配合,Polly 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

如果你想要这种形式的帮助模式,哪些呼叫者可以用于TaskTask<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发出的调用,建议的模式是将Polly策略放在DelegatingHandler中,如@ MuhammedRehanSaeed的回答here中所述。 ASP.NET Core 2.1支持创建此类DelegatingHandler s using IHttpClientFactory的简明声明。

答案 1 :(得分:0)

我认为你想要的是

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());
}