Parfois, j'ai besoin de réessayer une opération plusieurs fois avant d'abandonner. Mon code est comme:
int retries = 3;
while(true) {
try {
DoSomething();
break; // success!
} catch {
if(--retries == 0) throw;
else Thread.Sleep(1000);
}
}
Je voudrais réécrire ceci dans une fonction de nouvelle tentative générale comme:
TryThreeTimes(DoSomething);
Est-ce possible en C #? Quel serait le code de la TryThreeTimes()
méthode?
Réponses:
Les instructions catch de couverture qui relancent simplement le même appel peuvent être dangereuses si elles sont utilisées comme mécanisme général de gestion des exceptions. Cela dit, voici un wrapper de nouvelle tentative basé sur lambda que vous pouvez utiliser avec n'importe quelle méthode. J'ai choisi de prendre en compte le nombre de tentatives et le délai d'expiration des tentatives comme paramètres pour un peu plus de flexibilité:
Vous pouvez maintenant utiliser cette méthode utilitaire pour exécuter la logique de nouvelle tentative:
ou:
ou:
Ou vous pourriez même faire une
async
surcharge.la source
Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Vous devriez essayer Polly . C'est une bibliothèque .NET écrite par moi qui permet aux développeurs d'exprimer de manière fluide des politiques de gestion d'exceptions transitoires telles que Retry, Retry Forever, Wait and Retry ou Circuit Breaker.
Exemple
la source
C'est peut-être une mauvaise idée. Premièrement, elle est emblématique de la maxime "la définition de la folie fait deux fois la même chose et attend des résultats différents à chaque fois". Deuxièmement, ce modèle de codage ne se compose pas bien avec lui-même. Par exemple:
Supposons que votre couche matérielle réseau renvoie un paquet trois fois en cas d'échec, en attendant, disons, une seconde entre les échecs.
Supposons maintenant que la couche logicielle renvoie trois fois une notification d'échec lors d'une défaillance de paquet.
Supposons maintenant que la couche de notification réactive la notification trois fois en cas d'échec de remise de la notification.
Supposons maintenant que la couche de rapport d'erreurs réactive la couche de notification trois fois en cas d'échec de notification.
Supposons maintenant que le serveur Web réactive le rapport d'erreur trois fois en cas d'échec de l'erreur.
Supposons maintenant que le client Web renvoie la demande trois fois lorsqu'il obtient une erreur du serveur.
Supposons maintenant que la ligne sur le commutateur réseau qui est censée acheminer la notification à l'administrateur soit débranchée. Quand l'utilisateur du client Web reçoit-il enfin son message d'erreur? Je le fais environ douze minutes plus tard.
De peur que vous ne pensiez que ce n'est qu'un exemple stupide: nous avons vu ce bogue dans le code client, bien que bien pire que ce que j'ai décrit ici. Dans le code client particulier, l'écart entre la condition d'erreur qui se produit et celle qui est finalement signalée à l'utilisateur est de plusieurs semaines car de nombreuses couches sont automatiquement réessayées avec des attentes. Imaginez ce qui se passerait s'il y avait dix tentatives au lieu de trois .
Habituellement, la bonne chose à faire avec une condition d'erreur est de la signaler immédiatement et de laisser l'utilisateur décider quoi faire. Si l'utilisateur souhaite créer une stratégie de nouvelles tentatives automatiques, laissez-le créer cette stratégie au niveau approprié dans l'abstraction logicielle.
la source
Ensuite, vous appelleriez:
...Ou bien...
Une option plus flexible:
A utiliser comme:
Une version plus moderne avec support pour async / wait:
A utiliser comme:
la source
--retryCount <= 0
car cela continuera indéfiniment si vous souhaitez désactiver les tentatives en le définissant sur 0. Techniquement, le termeretryCount
n'est pas un très bon nom, car il ne réessayera pas si vous le définissez sur 1. soit renommer àtryCount
ou mettre le - derrière.Thread.Sleep
. Les alternatives sont d'utiliser des minuteries, ou plus probablement de nos joursasync
pour réessayer, avecTask.Delay
.returns true
?Func<bool>
Le bloc d'application de gestion des erreurs transitoires fournit une collection extensible de stratégies de nouvelle tentative, notamment:
Il comprend également une collection de stratégies de détection d'erreurs pour les services basés sur le cloud.
Pour plus d'informations, voir ce chapitre du Guide du développeur.
Disponible via NuGet (recherchez « topaze »).
la source
Autoriser les fonctions et réessayer les messages
la source
max retries
?Vous pouvez également envisager d'ajouter le type d'exception pour lequel vous souhaitez réessayer. Par exemple, s'agit-il d'une exception de délai d'attente que vous souhaitez réessayer? Une exception de base de données?
Vous pouvez également noter que tous les autres exemples ont un problème similaire avec le test de nouvelles tentatives == 0 et réessayez à l'infini ou ne déclenchez pas d'exceptions lorsque vous donnez une valeur négative. Sleep (-1000) échouera également dans les blocs catch ci-dessus. Cela dépend de la façon dont vous vous attendez à ce que les gens soient stupides, mais la programmation défensive ne fait jamais de mal.
la source
Je suis fan des méthodes de récursivité et d'extension, voici donc mes deux cents:
la source
Sur la base des travaux précédents, j'ai pensé à améliorer la logique des nouvelles tentatives de trois manières:
En faire une
Action
méthode d'extensionLa méthode peut alors être invoquée comme telle (des méthodes anonymes peuvent également être utilisées, bien sûr):
la source
Restez simple avec C # 6.0
la source
Utilisez Polly
https://github.com/App-vNext/Polly-Samples
Voici un nouveau générique que j'utilise avec Polly
Utilisez-le comme ça
la source
Implémenté la réponse de LBushkin de la dernière façon:
et pour l'utiliser:
alors que la fonction
[TaskFunction]
peut être soitTask<T>
ou justeTask
.la source
Je mettrais en œuvre ceci:
Je n'utiliserais pas les exceptions comme elles sont utilisées dans les autres exemples. Il me semble que si nous nous attendons à la possibilité qu'une méthode ne réussisse pas, son échec ne fait pas exception. Ainsi, la méthode que j'appelle doit retourner true si elle réussit et false si elle échoue.
Pourquoi est-ce un
Func<bool, bool>
et pas seulement unFunc<bool>
? Donc, si je veux qu'une méthode puisse lever une exception en cas d'échec, j'ai un moyen de l'informer que c'est le dernier essai.Je pourrais donc l'utiliser avec du code comme:
ou
Si passer un paramètre que la méthode n'utilise pas s'avère maladroit, il est trivial d'implémenter une surcharge
Retry
qui en prend juste unFunc<bool>
aussi.la source
StopIteration
exception par Python ), et il y a une raison.TryDo
modèle de méthode est une pente glissante. Avant de vous en rendre compte, toute votre pile d'appels sera constituée deTryDo
méthodes. Des exceptions ont été inventées pour éviter un tel gâchis.L'interruption exponentielle est une bonne stratégie de nouvelle tentative que de simplement essayer x nombre de fois. Vous pouvez utiliser une bibliothèque comme Polly pour l'implémenter.
la source
Pour ceux qui souhaitent avoir à la fois la possibilité de réessayer sur n'importe quelle exception ou de définir explicitement le type d'exception, utilisez ceci:
la source
J'avais besoin d'une méthode qui prend en charge l'annulation, alors que j'y étais, j'ai ajouté la prise en charge du retour des échecs intermédiaires.
Vous pouvez utiliser la
Retry
fonction comme ceci, réessayez 3 fois avec un délai de 10 secondes mais sans annulation.Ou, réessayez éternellement toutes les cinq secondes, sauf annulation.
Comme vous pouvez le deviner, dans mon code source, j'ai surchargé la
Retry
fonction pour prendre en charge les différents types de delgate que je souhaite utiliser.la source
Cette méthode permet de réessayer sur certains types d'exceptions (lance immédiatement d'autres).
Exemple d'utilisation:
la source
Mise à jour après 6 ans: maintenant je considère que l'approche ci-dessous est assez mauvaise. Pour créer une logique de nouvelle tentative, nous devons envisager d'utiliser une bibliothèque comme Polly.
Ma
async
mise en œuvre de la méthode de nouvelle tentative:Points clés: j'ai utilisé
.ConfigureAwait(false);
et à laFunc<dynamic>
placeFunc<T>
la source
Task.Delay
est appelé sans raison.Ou que diriez-vous de le faire un peu plus net ....
Je pense que les exceptions devraient généralement être évitées en tant que mécanisme, à moins que vous ne les dépassiez (comme la construction d'une bibliothèque que d'autres personnes peuvent utiliser). Pourquoi ne pas simplement
DoSomething()
renvoyer la commandetrue
si elle a réussi etfalse
sinon?EDIT: Et cela peut être encapsulé dans une fonction comme d'autres l'ont suggéré. Le seul problème est que si vous n'écrivez pas la
DoSomething()
fonction vous-mêmela source
J'ai eu besoin de passer un paramètre à ma méthode pour réessayer et avoir une valeur de résultat; j'ai donc besoin d'une expression .. Je construis cette classe qui fait le travail (elle est inspirée de celle de LBushkin) vous pouvez l'utiliser comme ceci:
ps. la solution de LBushkin fait une nouvelle tentative = D
la source
J'ajouterais le code suivant à la réponse acceptée
Fondamentalement, le code ci-dessus rend la
Retry
classe générique afin que vous puissiez passer le type de l'exception que vous souhaitez intercepter pour réessayer.Maintenant, utilisez-le presque de la même manière, mais en spécifiant le type d'exception
la source
action
ne lance pas,Do
retourne doncbreak
loin de lafor
boucle.Thread.Sleep
est appelé sans raison.Je sais que cette réponse est très ancienne, mais je voulais juste faire un commentaire à ce sujet car j'ai rencontré des problèmes en les utilisant pendant que, faites, quelle que soit la déclaration avec les compteurs.
Au fil des ans, je me suis fixé une meilleure approche, je pense. C'est d'utiliser une sorte d'agrégation d'événements comme une extension réactive "Sujet" ou similaire. Lorsqu'un essai échoue, vous publiez simplement un événement indiquant l'échec de l'essai et demandez à la fonction d'agrégateur de replanifier l'événement. Cela vous permet de contrôler beaucoup plus la relance sans polluer l'appel lui-même avec un tas de boucles de relance et ce qui ne l'est pas. Vous n'attachez pas non plus un seul fil avec un tas de fils en sommeil.
la source
Faites-le simplement en C #, Java ou d'autres langages:
et utilisez-le très simplement dans votre code:
ou vous pouvez l'utiliser dans des méthodes récursives:
la source
la source
Request.BadRequest
?Assistant de nouvelle tentative: une implémentation java générique qui contient à la fois des tentatives de retour et de type void.
Usage:
la source
Voici une version
async
/await
qui regroupe les exceptions et prend en charge l'annulation.la source
la source
throw;
c'est la seule façon de terminer la boucle infinie, cette méthode implémente en fait "essayer jusqu'à ce qu'elle échoue N fois" et non pas "essayer jusqu'àN
fois jusqu'à ce qu'elle réussisse". Vous avez besoin d'unbreak;
oureturn;
après l'appelThingToTryDelegate();
sinon il sera appelé en continu s'il n'échoue jamais. De plus, cela ne se compilera pas car le premier paramètre deTryNTimes
n'a pas de nom. -1.J'ai écrit une petite classe basée sur les réponses publiées ici. J'espère que cela aidera quelqu'un: https://github.com/natenho/resiliency
la source
J'ai implémenté une version asynchrone de la réponse acceptée comme ça - et cela semble bien fonctionner - des commentaires?
Et, appelez-le simplement comme ceci:
la source
Thread.Sleep
? Le blocage d'un thread annule les avantages de l'asynchronie. Je suis également sûr que laTask DoAsync()
version devrait accepter un argument de typeFunc<Task>
.