有时,我需要在放弃之前将一个操作重试几次。我的代码是:
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));
下面是一个async/await版本,它可以聚合异常并支持取消。
/// <seealso href="https://learn.microsoft.com/en-us/azure/architecture/patterns/retry"/>
protected static async Task<T> DoWithRetry<T>( Func<Task<T>> action, CancellationToken cancelToken, int maxRetries = 3 )
{
var exceptions = new List<Exception>();
for ( int retries = 0; !cancelToken.IsCancellationRequested; retries++ )
try {
return await action().ConfigureAwait( false );
} catch ( Exception ex ) {
exceptions.Add( ex );
if ( retries < maxRetries )
await Task.Delay( 500, cancelToken ).ConfigureAwait( false ); //ease up a bit
else
throw new AggregateException( "Retry limit reached", exceptions );
}
exceptions.Add( new OperationCanceledException( cancelToken ) );
throw new AggregateException( "Retry loop was canceled", exceptions );
}
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);
6年后更新:现在我认为下面的方法非常糟糕。为了创建重试逻辑,我们应该考虑使用Polly这样的库。
重试方法的异步实现:
public static async Task<T> DoAsync<T>(Func<dynamic> action, TimeSpan retryInterval, int retryCount = 3)
{
var exceptions = new List<Exception>();
for (int retry = 0; retry < retryCount; retry++)
{
try
{
return await action().ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
await Task.Delay(retryInterval).ConfigureAwait(false);
}
throw new AggregateException(exceptions);
}
重点:我使用。configureawait (false);和Func<dynamic>代替Func<T>
我需要传递一些参数给我的方法来重试,并有一个结果值;所以我需要一个表达。
我建立了这个类来做这个工作(它是受布什金的启发)
你可以这样使用它:
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