有时,我需要在放弃之前将一个操作重试几次。我的代码是:
int retries = 3;
while(true) {
try {
DoSomething();
break; // success!
} catch {
if(--retries == 0) throw;
else Thread.Sleep(1000);
}
}
我想在一个通用的重试函数中重写这个:
TryThreeTimes(DoSomething);
这在c#中可行吗?TryThreeTimes()方法的代码是什么?
public void TryThreeTimes(Action action)
{
var tries = 3;
while (true) {
try {
action();
break; // success!
} catch {
if (--tries == 0)
throw;
Thread.Sleep(1000);
}
}
}
然后你会呼叫:
TryThreeTimes(DoSomething);
...或者……
TryThreeTimes(() => DoSomethingElse(withLocalVariable));
一个更灵活的选择:
public void DoWithRetry(Action action, TimeSpan sleepPeriod, int tryCount = 3)
{
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true) {
try {
action();
break; // success!
} catch {
if (--tryCount == 0)
throw;
Thread.Sleep(sleepPeriod);
}
}
}
用作:
DoWithRetry(DoSomething, TimeSpan.FromSeconds(2), tryCount: 10);
支持async/await的更现代的版本:
public async Task DoWithRetryAsync(Func<Task> action, TimeSpan sleepPeriod, int tryCount = 3)
{
if (tryCount <= 0)
throw new ArgumentOutOfRangeException(nameof(tryCount));
while (true) {
try {
await action();
return; // success!
} catch {
if (--tryCount == 0)
throw;
await Task.Delay(sleepPeriod);
}
}
}
用作:
await DoWithRetryAsync(DoSomethingAsync, TimeSpan.FromSeconds(2), tryCount: 10);
重试助手:一个通用的java实现,它包含可返回类型和无效类型的重试。
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RetryHelper {
private static final Logger log = LoggerFactory.getLogger(RetryHelper.class);
private int retryWaitInMS;
private int maxRetries;
public RetryHelper() {
this.retryWaitInMS = 300;
this.maxRetries = 3;
}
public RetryHelper(int maxRetry) {
this.maxRetries = maxRetry;
this.retryWaitInMS = 300;
}
public RetryHelper(int retryWaitInSeconds, int maxRetry) {
this.retryWaitInMS = retryWaitInSeconds;
this.maxRetries = maxRetry;
}
public <T> T retryAndReturn(Supplier<T> supplier) {
try {
return supplier.get();
} catch (Exception var3) {
return this.retrySupplier(supplier);
}
}
public void retry(Runnable runnable) {
try {
runnable.run();
} catch (Exception var3) {
this.retrySupplier(() -> {
runnable.run();
return null;
});
}
}
private <T> T retrySupplier(Supplier<T> supplier) {
log.error("Failed <TASK>, will be retried " + this.maxRetries + " times.");
int retryCounter = 0;
while(retryCounter < this.maxRetries) {
try {
return supplier.get();
} catch (Exception var6) {
++retryCounter;
log.error("<TASK> failed on retry: " + retryCounter + " of " + this.maxRetries + " with error: " + var6.getMessage());
if (retryCounter >= this.maxRetries) {
log.error("Max retries exceeded.");
throw var6;
}
try {
Thread.sleep((long)this.retryWaitInMS);
} catch (InterruptedException var5) {
var5.printStackTrace();
}
}
}
return supplier.get();
}
public int getRetryWaitInMS() {
return this.retryWaitInMS;
}
public int getMaxRetries() {
return this.maxRetries;
}
}
用法:
try {
returnValue = new RetryHelper().retryAndReturn(() -> performSomeTask(args));
//or no return type:
new RetryHelper().retry(() -> mytask(args));
} catch(Exception ex){
log.error(e.getMessage());
throw new CustomException();
}
在之前工作的基础上,我考虑用三种方式增强重试逻辑:
Specifying what exception type to catch/retry. This is the primary enhacement as retrying for any exception is just plain wrong.
Not nesting the last try in a try/catch, achieving slightly better performance
Making it an Action extension method
static class ActionExtensions
{
public static void InvokeAndRetryOnException<T> (this Action action, int retries, TimeSpan retryDelay) where T : Exception
{
if (action == null)
throw new ArgumentNullException("action");
while( retries-- > 0 )
{
try
{
action( );
return;
}
catch (T)
{
Thread.Sleep( retryDelay );
}
}
action( );
}
}
然后可以像这样调用方法(当然也可以使用匿名方法):
new Action( AMethodThatMightThrowIntermittentException )
.InvokeAndRetryOnException<IntermittentException>( 2, TimeSpan.FromSeconds( 1 ) );
我需要传递一些参数给我的方法来重试,并有一个结果值;所以我需要一个表达。
我建立了这个类来做这个工作(它是受布什金的启发)
你可以这样使用它:
static void Main(string[] args)
{
// one shot
var res = Retry<string>.Do(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
// delayed execute
var retry = new Retry<string>(() => retryThis("try"), 4, TimeSpan.FromSeconds(2), fix);
var res2 = retry.Execute();
}
static void fix()
{
Console.WriteLine("oh, no! Fix and retry!!!");
}
static string retryThis(string tryThis)
{
Console.WriteLine("Let's try!!!");
throw new Exception(tryThis);
}
public class Retry<TResult>
{
Expression<Func<TResult>> _Method;
int _NumRetries;
TimeSpan _RetryTimeout;
Action _OnFailureAction;
public Retry(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
{
_Method = method;
_NumRetries = numRetries;
_OnFailureAction = onFailureAction;
_RetryTimeout = retryTimeout;
}
public TResult Execute()
{
TResult result = default(TResult);
while (_NumRetries > 0)
{
try
{
result = _Method.Compile()();
break;
}
catch
{
_OnFailureAction();
_NumRetries--;
if (_NumRetries <= 0) throw; // improved to avoid silent failure
Thread.Sleep(_RetryTimeout);
}
}
return result;
}
public static TResult Do(Expression<Func<TResult>> method, int numRetries, TimeSpan retryTimeout, Action onFailureAction)
{
var retry = new Retry<TResult>(method, numRetries, retryTimeout, onFailureAction);
return retry.Execute();
}
}
ps。
b布什金的解再重试一次=D
这可能是个坏主意。首先,这是一句格言的象征:“疯狂的定义是做同一件事两次,每次都期待不同的结果”。其次,这种编码模式本身不能很好地组合。例如:
假设您的网络硬件层在失败时重发一个数据包三次,在两次失败之间等待一秒钟。
现在假设软件层在包失败时重发关于失败的通知三次。
现在假设通知层在通知传递失败时重新激活通知三次。
现在假设错误报告层在通知失败时重新激活通知层三次。
现在假设web服务器在错误失败时重新激活错误报告三次。
现在假设web客户端在从服务器得到错误后重新发送请求三次。
现在假设网络交换机上用来将通知路由到管理员的线路被拔掉。web客户端的用户什么时候最终得到错误消息?我大约12分钟后到。
以免您认为这只是一个愚蠢的例子:我们已经在客户代码中看到了这个错误,尽管比我在这里描述的要严重得多。在特定的客户代码中,错误条件发生和最终报告给用户之间的间隔是几个星期,因为有很多层自动重试并等待。想象一下,如果是十次而不是三次,会发生什么。
通常正确的处理错误的方法是立即报告它,让用户决定怎么做。如果用户想要创建自动重试的策略,让他们在软件抽象的适当级别上创建该策略。