有时,我需要在放弃之前将一个操作重试几次。我的代码是:
int retries = 3;
while(true) {
try {
DoSomething();
break; // success!
} catch {
if(--retries == 0) throw;
else Thread.Sleep(1000);
}
}
我想在一个通用的重试函数中重写这个:
TryThreeTimes(DoSomething);
这在c#中可行吗?TryThreeTimes()方法的代码是什么?
我将在接受的答案中添加以下代码
public static class Retry<TException> where TException : Exception //ability to pass the exception type
{
//same code as the accepted answer ....
public static T Do<T>(Func<T> action, TimeSpan retryInterval, int retryCount = 3)
{
var exceptions = new List<Exception>();
for (int retry = 0; retry < retryCount; retry++)
{
try
{
return action();
}
catch (TException ex) //Usage of the exception type
{
exceptions.Add(ex);
Thread.Sleep(retryInterval);
}
}
throw new AggregateException(String.Format("Failed to excecute after {0} attempt(s)", retryCount), exceptions);
}
}
基本上,上面的代码使Retry类成为泛型,这样您就可以传递想要捕获的异常类型进行重试。
现在,以几乎相同的方式使用它,但指定异常类型
Retry<EndpointNotFoundException>.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
如果将简单地重试同一个调用的通用catch语句用作通用异常处理机制,则可能是危险的。话虽如此,这里有一个基于lambda的重试包装器,您可以将其用于任何方法。为了获得更大的灵活性,我选择将重试次数和重试超时作为参数:
public static class Retry
{
public static void Do(
Action action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
Do<object>(() =>
{
action();
return null;
}, retryInterval, maxAttemptCount);
}
public static T Do<T>(
Func<T> action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
Thread.Sleep(retryInterval);
}
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
你现在可以使用这个实用程序方法来执行重试逻辑:
Retry.Do(() => SomeFunctionThatCanFail(), TimeSpan.FromSeconds(1));
or:
Retry.Do(SomeFunctionThatCanFail, TimeSpan.FromSeconds(1));
or:
int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);
或者你甚至可以做一个异步重载。
对于那些既想对任何异常进行重试,又想显式设置异常类型的人,可以使用以下方法:
public class RetryManager
{
public void Do(Action action,
TimeSpan interval,
int retries = 3)
{
Try<object, Exception>(() => {
action();
return null;
}, interval, retries);
}
public T Do<T>(Func<T> action,
TimeSpan interval,
int retries = 3)
{
return Try<T, Exception>(
action
, interval
, retries);
}
public T Do<E, T>(Func<T> action,
TimeSpan interval,
int retries = 3) where E : Exception
{
return Try<T, E>(
action
, interval
, retries);
}
public void Do<E>(Action action,
TimeSpan interval,
int retries = 3) where E : Exception
{
Try<object, E>(() => {
action();
return null;
}, interval, retries);
}
private T Try<T, E>(Func<T> action,
TimeSpan interval,
int retries = 3) where E : Exception
{
var exceptions = new List<E>();
for (int retry = 0; retry < retries; retry++)
{
try
{
if (retry > 0)
Thread.Sleep(interval);
return action();
}
catch (E ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
我使用Polly实现了该模式的两个实现。其一是异步。
我的同步方法是基于Erik Bergstedt的回答
public static T Retry<T>(Func<T> action, TimeSpan retryWait, int retryCount = 0)
{
PolicyResult<T> policyResult = Policy
.Handle<ApiException>(ex => ex.ResponseCode == (int)HttpStatusCode.TooManyRequests)
.WaitAndRetry(retryCount, retryAttempt => retryWait)
.ExecuteAndCapture(action);
if (policyResult.Outcome == OutcomeType.Failure)
{
throw policyResult.FinalException;
}
return policyResult.Result;
}
异步:
public static async Task<T> RetryAsync<T>(Func<Task<T>> action, TimeSpan retryWait, int retryCount = 0)
{
PolicyResult<T> policyResult = await Policy
.Handle<ApiException>(ex => ex.ResponseCode == (int)HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(retryCount, retryAttempt => retryWait)
.ExecuteAndCaptureAsync(action);
if (policyResult.Outcome == OutcomeType.Failure)
{
throw policyResult.FinalException;
}
return policyResult.Result;
}
允许传入异常类型以及异常类型的lambda也很容易。
以最新的方式实现了LBushkin的答案:
public static async Task Do(Func<Task> task, TimeSpan retryInterval, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
await task();
return;
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
public static async Task<T> Do<T>(Func<Task<T>> task, TimeSpan retryInterval, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
return await task();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
要使用它:
await Retry.Do([TaskFunction], retryInterval, retryAttempts);
而函数[TaskFunction]可以是Task<T>,也可以只是Task。