我正在尝试将现有功能转换为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
答案 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
最初看起来很有吸引力,因为方法可以与Task
或Task<T>
一起使用。 Jon Skeet解释here和here为什么它不能与异步一起使用。您建议的帮助方法签名本身不是异步的:
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
。
如果你想要这种形式的帮助模式,哪些呼叫者可以用于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
发出的调用,建议的模式是将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());
}