有时,我需要在放弃之前将一个操作重试几次。我的代码是:

int retries = 3;
while(true) {
  try {
    DoSomething();
    break; // success!
  } catch {
    if(--retries == 0) throw;
    else Thread.Sleep(1000);
  }
}

我想在一个通用的重试函数中重写这个:

TryThreeTimes(DoSomething);

这在c#中可行吗?TryThreeTimes()方法的代码是什么?


当前回答

下面是一个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 );
}

其他回答

或者做得更整洁一点....

int retries = 3;
while (retries > 0)
{
  if (DoSomething())
  {
    retries = 0;
  }
  else
  {
    retries--;
  }
}

我认为抛出异常通常应该避免作为一种机制,除非你想在边界之间传递异常(比如构建一个其他人可以使用的库)。为什么不只是让DoSomething()命令在成功时返回true,否则返回false呢?

编辑:这也可以像其他人建议的那样封装在函数中。唯一的问题是如果您不是自己编写DoSomething()函数

如果将简单地重试同一个调用的通用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);

或者你甚至可以做一个异步重载。

使用c# 6.0保持简单

public async Task<T> Retry<T>(Func<T> action, TimeSpan retryInterval, int retryCount)
{
    try
    {
        return action();
    }
    catch when (retryCount != 0)
    {
        await Task.Delay(retryInterval);
        return await Retry(action, retryInterval, --retryCount);
    }
}

我是递归和扩展方法的粉丝,所以这里是我的观点:

public static void InvokeWithRetries(this Action @this, ushort numberOfRetries)
{
    try
    {
        @this();
    }
    catch
    {
        if (numberOfRetries == 0)
            throw;

        InvokeWithRetries(@this, --numberOfRetries);
    }
}

用c#、Java或其他语言简单地完成:

  internal class ShouldRetryHandler {
    private static int RETRIES_MAX_NUMBER = 3;
    private static int numberTryes;

    public static bool shouldRetry() {
        var statusRetry = false;

        if (numberTryes< RETRIES_MAX_NUMBER) {
            numberTryes++;
            statusRetry = true;
            //log msg -> 'retry number' + numberTryes

        }

        else {
            statusRetry = false;
            //log msg -> 'reached retry number limit' 
        }

        return statusRetry;
    }
}

并在你的代码中简单地使用它:

 void simpleMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    //do some repetitive work
     }

    //some code    
    }

或者你可以在递归方法中使用它:

void recursiveMethod(){
    //some code

    if(ShouldRetryHandler.shouldRetry()){
    recursiveMethod();
     }

    //some code    
    }