Polly的带有Function的重试策略没有等待结果。

6

我正在尝试将我的现有函数转换为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


1
几乎从不使用 Wait。尝试将函数设置为异步并在内部等待。 - JamesFaix
你只是返回了 task 局部变量,但它并没有被你的策略使用。Polly 会自行调用该函数。 - JamesFaix
@JamesFaix 这是一个我不能违反的合同 public static T Execute<T>(Func<T> func) where T : Task - RaceBase
2个回答

6

对于通过硬编码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在这里这里解释了为什么它不适用于异步操作。您提出的辅助方法签名本身不是异步的:

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。如果你想要这种形式的帮助程序,调用者可以用它来处理返回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's 在这里描述的答案中。ASP.NET Core 2.1支持使用IHttpClientFactory创建这样的DelegatingHandler的简明声明 here


1
我想您想要的是:
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());
}

我开始处理这个问题,但遇到了一个问题:“无法将类型Task隐式转换为T”。当我进行显式转换时,又遇到了运行时异常。 - RaceBase
也许你只需要 Execute 而不是 ExecuteAsync - JamesFaix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接