web-dev-qa-db-fra.com

Comment implémentez-vous un re-try-catch?

Try-catch est conçu pour aider à la gestion des exceptions. Cela signifie en quelque sorte que cela aidera notre système à être plus robuste: essayez de récupérer d'un événement imprévu. 

Nous soupçonnons que quelque chose pourrait se produire lors de l'exécution et de l'instruction (envoi d'un message), afin que cela soit inclus dans l'essai. Si quelque chose d'inattendu se produit, nous pouvons faire quelque chose: nous écrivons la prise. Je ne pense pas que nous ayons appelé pour simplement enregistrer l'exception. Je pense que le blocage est destiné à nous donner l’occasion de nous remettre de l’erreur.

Maintenant, supposons que nous récupérions de l'erreur parce que nous pourrions réparer ce qui n'allait pas. Ça pourrait être super sympa de faire un nouvel essai:

try{ some_instruction(); }
catch (NearlyUnexpectedException e){
   fix_the_problem();
   retry;
}

Cela tomberait rapidement dans l'éternelle boucle, mais supposons que le problème fix_the_problem retourne true, puis nous réessayons. Étant donné qu’il n’existe pas de solution de ce type en Java, comment VOUS résoudre ce problème? Quel serait votre meilleur code de conception pour résoudre ce problème?

C'est comme une question philosophique, étant donné que je sais déjà que ce que je demande n'est pas directement pris en charge par Java.

162
Andres Farias

Vous devez inclure votre try-catch dans une boucle while comme ceci: -

int count = 0;
int maxTries = 3;
while(true) {
    try {
        // Some Code
        // break out of loop, or return, on success
    } catch (SomeException e) {
        // handle exception
        if (++count == maxTries) throw e;
    }
}

J'ai pris count et maxTries pour éviter de courir dans une boucle infinie, au cas où l'exception continue de se produire dans votre try block.

249
Rohit Jain

Solution "d'entreprise" obligatoire:

public abstract class Operation {
    abstract public void doIt();
    public void handleException(Exception cause) {
        //default impl: do nothing, log the exception, etc.
    }
}

public class OperationHelper {
    public static void doWithRetry(int maxAttempts, Operation operation) {
        for (int count = 0; count < maxAttempts; count++) {
            try {
                operation.doIt();
                count = maxAttempts; //don't retry
            } catch (Exception e) {
                operation.handleException(e);
            }
        }
    }
}

Et appeler:

OperationHelper.doWithRetry(5, new Operation() {
    @Override public void doIt() {
        //do some stuff
    }
    @Override public void handleException(Exception cause) {
        //recover from the Exception
    }
});
46
ach

Comme d'habitude, la meilleure conception dépend des circonstances. D'habitude, j'écris quelque chose comme:

for (int retries = 0;; retries++) {
    try {
        return doSomething();
    } catch (SomeException e) {
        if (retries < 6) {
            continue;
        } else {
            throw e;
        }
    }
}
31
meriton

Bien que try/catch dans while soit bien connu et bonne stratégie, je tiens à vous suggérer un appel récursif:

void retry(int i, int limit) {
    try {

    } catch (SomeException e) {
        // handle exception
        if (i >= limit) {
            throw e;  // variant: wrap the exception, e.g. throw new RuntimeException(e);
        }
        retry(i++, limit);
    }
}
19
AlexR

Vous pouvez utiliser les annotations AOP et Java de jcabi-aspects (je suis développeur):

@RetryOnFailure(attempts = 3, delay = 5)
public String load(URL url) {
  return url.openConnection().getContent();
}

Vous pouvez également utiliser les annotations @Loggable et @LogException.

13
yegor256

Votre scénario exact géré via Failsafe :

RetryPolicy retryPolicy = new RetryPolicy()
  .retryOn(NearlyUnexpectedException.class);

Failsafe.with(retryPolicy)
  .onRetry((r, f) -> fix_the_problem())
  .run(() -> some_instruction());

Assez simple.

11
Jonathan

La plupart de ces réponses sont essentiellement les mêmes. Le mien est aussi, mais c'est la forme que j'aime

boolean completed = false;
Throwable lastException = null;
for (int tryCount=0; tryCount < config.MAX_SOME_OPERATION_RETRIES; tryCount++)
{
    try {
        completed = some_operation();
        break;
    }
    catch (UnlikelyException e) {
        lastException = e;
        fix_the_problem();
    }
}
if (!completed) {
    reportError(lastException);
}
6
Stephen P

Utilisez une boucle while avec l'indicateur local status. Initialisez l'indicateur en tant que false et définissez-le sur true lorsque l'opération réussit, par exemple. au dessous de:

  boolean success  = false;
  while(!success){
     try{ 
         some_instruction(); 
         success = true;
     } catch (NearlyUnexpectedException e){
       fix_the_problem();
     }
  }

Cela va continuer à réessayer jusqu'à ce qu'il réussisse.

Si vous souhaitez réessayer uniquement un certain nombre de fois, utilisez également un compteur:

  boolean success  = false;
  int count = 0, MAX_TRIES = 10;
  while(!success && count++ < MAX_TRIES){
     try{ 
         some_instruction(); 
         success = true;
     } catch (NearlyUnexpectedException e){
       fix_the_problem();
     }
  }
  if(!success){
    //It wasn't successful after 10 retries
  }

Cela essaiera 10 fois maximum si cela ne réussit pas jusque-là et un sortira si cela réussit avant.

3
Yogendra Singh

Un moyen simple de résoudre le problème consiste à envelopper la boucle try/catch dans une boucle while et à maintenir un compte. De cette façon, vous pouvez éviter une boucle infinie en comparant un compte à une autre variable tout en conservant un journal de vos échecs. Ce n'est pas la solution la plus exquise, mais cela fonctionnerait.

2
Jordan Kaye

Si cela est utile, quelques options supplémentaires à prendre en compte, toutes jeté ensemble (fichier d'arrêt au lieu d'essais, veille, poursuite d'une boucle plus grande), toutes éventuellement utiles.

 bigLoop:
 while(!stopFileExists()) {
    try {
      // do work
      break;
    }
    catch (ExpectedExceptionType e) {

       // could sleep in here, too.

       // another option would be to "restart" some bigger loop, like
       continue bigLoop;
    }
    // ... more work
}
1
rogerdpack

Vous pouvez utiliser https://github.com/bnsd55/RetryCatch

Exemple:

RetryCatch retryCatchSyncRunnable = new RetryCatch();
        retryCatchSyncRunnable
                // For infinite retry times, just remove this row
                .retryCount(3)
                // For retrying on all exceptions, just remove this row
                .retryOn(ArithmeticException.class, IndexOutOfBoundsException.class)
                .onSuccess(() -> System.out.println("Success, There is no result because this is a runnable."))
                .onRetry((retryCount, e) -> System.out.println("Retry count: " + retryCount + ", Exception message: " + e.getMessage()))
                .onFailure(e -> System.out.println("Failure: Exception message: " + e.getMessage()))
                .run(new ExampleRunnable());

Au lieu de new ExampleRunnable(), vous pouvez transmettre votre propre fonction anonyme.

1
bnsd55

voici ma solution avec une approche très simple!

               while (true) {
                    try {
                        /// Statement what may cause an error;
                        break;
                    } catch (Exception e) {

                    }
                }
0
David Kayo

Voici une approche réutilisable et plus générique pour Java 8+ qui ne nécessite pas de bibliothèques externes:

public interface IUnreliable<T extends Exception>
{
    void tryRun ( ) throws T;
}

public static <T extends Exception> void retry (int retryCount, IUnreliable<T> runnable) throws T {
    for (int retries = 0;; retries++) {
        try {
            runnable.tryRun();
            return;
        } catch (Exception e) {
            if (retries < retryCount) {
                continue;
            } else {
                throw e;
            }
        }
    }
}

Usage:

@Test
public void demo() throws IOException {
    retry(3, () -> {
        new File("/tmp/test.txt").createNewFile();
    });
}
0
Jonas_Hess

Je ne suis pas sûr que ce soit la manière "professionnelle" de le faire et je ne suis pas tout à fait sûr que cela fonctionne pour tout.

boolean gotError = false;

do {
    try {
        // Code You're Trying
    } catch ( FileNotFoundException ex ) {
        // Exception
        gotError = true;
    }
} while ( gotError = true );
0
Josh

Utilisez un do-while pour concevoir un nouveau bloc.

boolean successful = false;
int maxTries = 3;
do{
  try {
    something();
    success = true;
  } catch(Me ifUCan) {
    maxTries--;
  }
} while (!successful || maxTries > 0)
0
Rahul Malhotra

C'est une vieille question mais une solution est toujours pertinente. Voici ma solution générique dans Java 8 sans utiliser de bibliothèque tierce:

public interface RetryConsumer<T> {
    T evaluate() throws Throwable;
}
public interface RetryPredicate<T> {
    boolean shouldRetry(T t);
}
public class RetryOperation<T> {
    private RetryConsumer<T> retryConsumer;
    private int noOfRetry;
    private int delayInterval;
    private TimeUnit timeUnit;
    private RetryPredicate<T> retryPredicate;
    private List<Class<? extends Throwable>> exceptionList;

    public static class OperationBuilder<T> {
        private RetryConsumer<T> iRetryConsumer;
        private int iNoOfRetry;
        private int iDelayInterval;
        private TimeUnit iTimeUnit;
        private RetryPredicate<T> iRetryPredicate;
        private Class<? extends Throwable>[] exceptionClasses;

        private OperationBuilder() {
        }

        public OperationBuilder<T> retryConsumer(final RetryConsumer<T> retryConsumer) {
            this.iRetryConsumer = retryConsumer;
            return this;
        }

        public OperationBuilder<T> noOfRetry(final int noOfRetry) {
            this.iNoOfRetry = noOfRetry;
            return this;
        }

        public OperationBuilder<T> delayInterval(final int delayInterval, final TimeUnit timeUnit) {
            this.iDelayInterval = delayInterval;
            this.iTimeUnit = timeUnit;
            return this;
        }

        public OperationBuilder<T> retryPredicate(final RetryPredicate<T> retryPredicate) {
            this.iRetryPredicate = retryPredicate;
            return this;
        }

        @SafeVarargs
        public final OperationBuilder<T> retryOn(final Class<? extends Throwable>... exceptionClasses) {
            this.exceptionClasses = exceptionClasses;
            return this;
        }

        public RetryOperation<T> build() {
            if (Objects.isNull(iRetryConsumer)) {
                throw new RuntimeException("'#retryConsumer:RetryConsumer<T>' not set");
            }

            List<Class<? extends Throwable>> exceptionList = new ArrayList<>();
            if (Objects.nonNull(exceptionClasses) && exceptionClasses.length > 0) {
                exceptionList = Arrays.asList(exceptionClasses);
            }
            iNoOfRetry = iNoOfRetry == 0 ? 1 : 0;
            iTimeUnit = Objects.isNull(iTimeUnit) ? TimeUnit.MILLISECONDS : iTimeUnit;
            return new RetryOperation<>(iRetryConsumer, iNoOfRetry, iDelayInterval, iTimeUnit, iRetryPredicate, exceptionList);
        }
    }

    public static <T> OperationBuilder<T> newBuilder() {
        return new OperationBuilder<>();
    }

    private RetryOperation(RetryConsumer<T> retryConsumer, int noOfRetry, int delayInterval, TimeUnit timeUnit,
                           RetryPredicate<T> retryPredicate, List<Class<? extends Throwable>> exceptionList) {
        this.retryConsumer = retryConsumer;
        this.noOfRetry = noOfRetry;
        this.delayInterval = delayInterval;
        this.timeUnit = timeUnit;
        this.retryPredicate = retryPredicate;
        this.exceptionList = exceptionList;
    }

    public T retry() throws Throwable {
        T result = null;
        int retries = 0;
        while (retries < noOfRetry) {
            try {
                result = retryConsumer.evaluate();
                if (Objects.nonNull(retryPredicate)) {
                    boolean shouldItRetry = retryPredicate.shouldRetry(result);
                    if (shouldItRetry) {
                        retries = increaseRetryCountAndSleep(retries);
                    } else {
                        return result;
                    }
                } else {
                    // no retry condition defined, no exception thrown. This is the desired result.
                    return result;
                }
            } catch (Throwable e) {
                retries = handleException(retries, e);
            }
        }
        return result;
    }

    private int handleException(int retries, Throwable e) throws Throwable {
        if (exceptionList.contains(e.getClass()) || (exceptionList.isEmpty())) {
            // exception is excepted, continue retry.
            retries = increaseRetryCountAndSleep(retries);
            if (retries == noOfRetry) {
                // evaluation is throwing exception, no more retry left. Throw it.
                throw e;
            }
        } else {
            // unexpected exception, no retry required. Throw it.
            throw e;
        }
        return retries;
    }

    private int increaseRetryCountAndSleep(int retries) {
        retries++;
        if (retries < noOfRetry && delayInterval > 0) {
            try {
                timeUnit.sleep(delayInterval);
            } catch (InterruptedException ignore) {
                Thread.currentThread().interrupt();
            }
        }
        return retries;
    }
}

Ayons un cas de test comme:

@Test
public void withPredicateAndException() {
    AtomicInteger integer = new AtomicInteger();
    try {
        Integer result = RetryOperation.<Integer>newBuilder()
                .retryConsumer(() -> {
                    int i = integer.incrementAndGet();
                    if (i % 2 == 1) {
                        throw new NumberFormatException("Very odd exception");
                    } else {
                        return i;
                    }
                })
                .noOfRetry(10)
                .delayInterval(10, TimeUnit.MILLISECONDS)
                .retryPredicate(value -> value <= 6)
                .retryOn(NumberFormatException.class, EOFException.class)
                .build()
                .retry();
        Assert.assertEquals(8, result.intValue());
    } catch (Throwable throwable) {
        Assert.fail();
    }
}
0
GirishB

Le problème avec les solutions restantes est que, la fonction de correspondant essaie en continu sans intervalle de temps, inondant ainsi la pile.

Pourquoi ne pas simplement tryer seulement chaque seconde et ad eternum ?

Voici une solution utilisant setTimeout et une fonction récursive: 

(function(){
  try{
    Run(); //tries for the 1st time, but Run() as function is not yet defined
  }
  catch(e){
    (function retry(){
      setTimeout(function(){
        try{
          console.log("trying...");
          Run();
          console.log("success!");
        }
        catch(e){
          retry(); //calls recursively
        }
      }, 1000); //tries every second
    }());
  }
})();



//after 5 seconds, defines Run as a global function
var Run;
setTimeout(function(){
  Run = function(){};
}, 5000);

Remplacez la fonction Run() par la fonction ou le code que vous souhaitez retry chaque seconde.

Tout ce que Try-Catch fait, c’est que votre programme échoue normalement. Dans une instruction catch, vous essayez généralement de consigner l'erreur et, éventuellement, d'annuler les modifications. 

bool finished = false;

while(finished == false)
{
    try
    {
        //your code here
        finished = true
    }
    catch(exception ex)
    {
        log.error("there was an error, ex");
    }
}
0
Sam I am

Spring AOP et solution basée sur des annotations:

Utilisation (@RetryOperation est notre annotation personnalisée pour le travail):

@RetryOperation(retryCount = 1, waitSeconds = 10)
boolean someMethod() throws Exception {
}

Pour ce faire, nous avons besoin de deux choses: 1. une interface d'annotation, et 2. un aspect de ressort. Voici un moyen de les implémenter:

L'interface d'annotation:

import Java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryOperation {
    int retryCount();
    int waitSeconds();
}

L'aspect printanier:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import Java.lang.reflect.Method;

@Aspect @Component 
public class RetryAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(RetryAspect.class);

    @Around(value = "@annotation(RetryOperation)")
    public Object retryOperation(ProceedingJoinPoint joinPoint) throws Throwable {

        Object response = null;
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RetryOperation annotation = method.getAnnotation(RetryOperation.class);
        int retryCount = annotation.retryCount();
        int waitSeconds = annotation.waitSeconds();
        boolean successful = false;

        do {
            try {
                response = joinPoint.proceed();
                successful = true;
            } catch (Exception ex) {
                LOGGER.info("Operation failed, retries remaining: {}", retryCount);
                retryCount--;
                if (retryCount < 0) {
                    throw ex;
                }
                if (waitSeconds > 0) {
                    LOGGER.info("Waiting for {} second(s) before next retry", waitSeconds);
                    Thread.sleep(waitSeconds * 1000l);
                }
            }
        } while (!successful);

        return response;
    }
}
0
Vivek Sethi

https://github.com/tusharmndr/retry-function-wrapper/tree/master/src/main/Java/io

int MAX_RETRY = 3; 
RetryUtil.<Boolean>retry(MAX_RETRY,() -> {
    //Function to retry
    return true;
});
0
tushar Mandar

Je sais qu'il y a déjà beaucoup de réponses similaires ici, et la mienne n'est pas très différente, mais je vais l'afficher quand même, car il s'agit d'un cas/problème spécifique.

Lorsque vous traitez avec le facebook Graph API dans PHP, vous obtenez parfois une erreur, mais réessayer immédiatement la même chose donnera un résultat positif (pour diverses raisons magiques Internet dépassant le cadre de cette question). Dans ce cas, il n'est pas nécessaire de corriger les erreurs, mais simplement d'essayer à nouveau car il y avait une sorte d '"erreur facebook".

Ce code est utilisé immédiatement après la création d'une session facebook:

//try more than once because sometimes "facebook error"
$attempt = 3;
while($attempt-- > 0)
{
    // To validate the session:
    try 
    {
        $facebook_session->validate();
        $attempt = 0;
    } 
    catch (Facebook\FacebookRequestException $ex)
    {
        // Session not valid, Graph API returned an exception with the reason.
        if($attempt <= 0){ echo $ex->getMessage(); }
    } 
    catch (\Exception $ex) 
    {
        // Graph API returned info, but it may mismatch the current app or have expired.
        if($attempt <= 0){ echo $ex->getMessage(); }
    }
}

De plus, en comptant le nombre de boucles for à zéro ($attempt--), il est très facile de modifier le nombre de tentatives ultérieures.

0
KnightHawk