Façon la plus propre d'écrire logique de nouvelle tentative?
Temps en temps j'ai un besoin de relancer 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 ce dans un général de réessayer de fonction comme:
TryThreeTimes(DoSomething);
Est-il possible en C#? Quel serait le code pour le TryThreeTimes()
méthode?
- Un cycle simple n'est pas suffisant? Pourquoi ne pas parcourir et exécuter la logique à plusieurs reprises?
- Personnellement, je serais extrêmement méfier d'une telle méthode d'assistance. Il est certainement possible de mettre en œuvre à l'aide de lambda, mais le modèle lui-même est extrêmement puant, de sorte que l'introduction d'une aide pour elle (ce qui implique qu'il est souvent répété) est en elle-même très suspect, et fortement allusion à la mauvaise conception globale.
- Dans mon cas, mon DoSomething()s sont en train de faire des trucs sur des machines distantes, telles que la suppression des fichiers, ou essayer de frapper un port réseau. Dans les deux cas, il existe d'importants problèmes de synchronisation lorsque DoSomething réussissent, et en raison de l'éloignement, il n'y a aucun cas je peux écouter. Alors oui, ses odeurs. Suggestions de bienvenue.
- pourquoi l'aide de nouvelles tentatives allusion à la mauvaise conception globale? Si vous écrivez beaucoup de code qui relie les points d'intégration puis à l'aide de tentatives est certainement un motif que vous devriez sérieusement envisager d'utiliser.
Vous devez vous connecter pour publier un commentaire.
Couverture instructions catch simplement réessayer la même appel peut être dangereux s'il est utilisé comme un mécanisme de gestion des exceptions générales. Cela dit, voici un lambda à base de réessayer wrapper que vous pouvez utiliser avec n'importe quelle méthode. J'ai choisi de facteur, le nombre de tentatives et le délai de tentatives comme des paramètres pour un peu plus de souplesse:
Vous pouvez maintenant utiliser cette méthode utilitaire pour effectuer logique de nouvelle tentative:
ou:
ou:
Ou vous pourriez même faire un
async
surcharge.if
de lado
boucle, pour l'amour de Dieu!action
plusieurs fois, même quand il n'y a pas d'exceptions. Sûrementaction
ne doit être appelé de nouveau si l'appel précédent(s) a échoué.Action
est un .NET framework type qui est accessible à partir de VB. VB2008 a aussi l'expression lambda, mais pas de déclaration de lambda (même si ce dernier sera en VB2010).while(true)
au lieu dewhile( numRetries-- > 0 )
et de diminuer les tentatives dans le ifif( numRetries-- <= 0 ) throw;
try { if (retry > 0) Thread.Sleep(retryInterval);
... Afin de ne pas retarder après la dernière tentative a été exécuté.Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
var ok = SomeFunction(1,2);
?async overload
?maxAttemptCount
, j'ai créé unIRetryHandler
à être utilisé dans cette méthode avec repli à un simple défaut de mise en œuvre. Elle doit prendre les exceptions en argument et qui doit compter le tente et quelle que soit l'infrastructure requise pour la manipulation. Cela vous permet un contrôle précis de la manipulation et de la stratégie de nouvelle tentative, tout en la gardant distincte de la logique de nouvelle tentative qui est simple.Vous devriez essayer Polly. C'est un .NET bibliothèque écrite par moi que permet aux développeurs d'exprimer transitoire de gestion des exceptions de politiques telles que la nouvelle tentative, nouvelle tentative pour Toujours, Attendre et Réessayer ou Disjoncteur dans un fluide manière.
Exemple
C'est peut-être une mauvaise idée. Tout d'abord, il est emblématique de la maxime "la définition de la folie est de faire deux fois la même chose et espérer des résultats différents à chaque fois". Deuxièmement, ce modèle de codage n'est pas bien composer avec lui-même. Par exemple:
Supposons que votre matériel réseau couche renvoie un paquet de trois fois en cas d'erreur, d'attente, de dire, d'une seconde entre les pannes.
Supposons maintenant que la couche de logiciel renvoie une notification à propos d'un échec à trois reprises sur le paquet échec.
Supposons maintenant que la notification de la couche réactive la notification à trois reprises sur une notification d'échec de remise.
Supposons maintenant que le rapport d'erreur de la couche réactive la notification de la couche trois fois sur une notification d'échec.
Et maintenant, supposons que le serveur web réactive le rapport d'erreur à trois reprises sur erreur échec.
Et maintenant, imaginons que le client web envoie à nouveau la demande trois fois sur une erreur du serveur.
Supposons maintenant que la ligne sur le commutateur de réseau qui est censé route de la notification de l'administrateur est débranché. Lorsque l'utilisateur du client web enfin à leur message d'erreur? Je le fais à environ douze minutes plus tard.
De peur que vous pensez que c'est juste un exemple stupide: nous avons vu ce bug dans le code client, bien loin, bien pire que ce que j'ai décrit ici. Dans le code du client, l'écart entre la condition d'erreur qui se passe et enfin de les signaler à l'utilisateur a été plusieurs semaines parce que tant de couches ont été automatiquement une nouvelle tentative avec attend. Imaginez ce qui se passerait s'il y avait dix tentatives au lieu de trois.
Généralement la bonne chose à faire avec une condition d'erreur est le signaler immédiatement et permettre à l'utilisateur de décider quoi faire. Si l'utilisateur veut créer une politique de tentatives automatiques, laissez-les créer que de la politique au niveau approprié dans le logiciel d'abstraction.
Ensuite, vous appelez le:
...ou sinon...
Un plus souples option:
À être utilisé comme:
Une version plus moderne avec un support pour async/await:
À être utilisé comme:
--retryCount <= 0
parce que cela va durer pour toujours, si vous souhaitez désactiver les tentatives par la valeur 0. Techniquement, le termeretryCount
n'est pas vraiment un bon nom, car il ne sera pas ré-essayer si vous le réglez à 1. renommez-le àtryCount
ou mettre le -- derrière.Thread.Sleep
. Des Alternatives à l'utilisation des minuteries, ou, plus probablement, aujourd'hui, d'utiliserasync
pour réessayer, avecTask.Delay
.returns true
?Func<bool>
La Transitoire De Gestion Des Pannes Bloc D'Application fournit un extensible de la collection de réessayer de stratégies, y compris:
Il comprend également une collection de détection des erreurs de stratégies pour les services basés sur le cloud.
Pour plus d'informations, voir ce chapitre du Guide du Développeur.
Disponible via NuGet (la recherche pour " topaze').
Permettant de fonctions et de réessayer de messages
max retries
?Vous pourriez aussi envisager d'ajouter le type d'exception vous voulez réessayer pour. Par exemple est-ce une exception délai vous voulez vous réessayer? Une base de données d'exception?
Vous pouvez également noter que tous les autres exemples ont un problème similaire avec les tests pour les tentatives == 0 et réessayer de l'infini ou de l'échec de lever des exceptions quand on donne une valeur négative. Aussi le Sommeil(-1000) échouera dans les captures blocs ci-dessus. Dépend de la façon dont 'bête' vous attendez les gens à être, mais une programmation défensive ne fait jamais de mal.
Je suis un fan de la récursivité et les méthodes d'extension, voici donc mes deux cents:
Appuyant sur les travaux antérieurs, j'ai pensé à l'amélioration de la logique de nouvelle tentative de trois façons:
Faire un
Action
méthode d'extensionLa méthode peut alors être invoquée comme (anonyme méthodes peuvent être utilisées, bien sûr):
Utilisation Polly
https://github.com/App-vNext/Polly-Samples
Ici est une tentative de nouvelle-générique-je utiliser avec Polly
L'utiliser comme ceci
Keep it simple avec C# 6.0
Mis en œuvre LBushkin la réponse à la dernière mode:
et de l'utiliser:
alors que la fonction
[TaskFunction]
peut êtreTask<T>
ou tout simplementTask
.J'aimerais mettre en œuvre cette:
Je ne voudrais pas utiliser les exceptions de la façon dont ils sont utilisés dans les autres exemples. Il me semble que si nous nous attendons à la possibilité qu'une méthode ne réussit pas, son échec n'est pas une exception. Si la méthode que je vais appeler doit retourner true si succès, et false en cas d'échec.
Pourquoi est-il un
Func<bool, bool>
et pas seulement unFunc<bool>
? De sorte que si je voulez une méthode pour être en mesure de lever une exception en cas d'échec, j'ai une façon de l'informer que c'est la dernière tentative.Donc je pourrais l'utiliser avec un code comme:
ou
Si le passage d'un paramètre que la méthode n'utilise pas de se révèle être maladroit, c'est trivial à mettre en œuvre une surcharge de
Retry
qui prend juste unFunc<bool>
ainsi.StopIteration
exception), et il y a une raison.Pour ceux qui veulent avoir à la fois la possibilité de reprendre sur toute exception ou de définir explicitement le type d'exception, utilisez ceci:
Mon
async
mise en œuvre de la nouvelle tentative de la méthode:Points clés: j'ai utilisé
.ConfigureAwait(false);
etFunc<dynamic>
au lieuFunc<T>
L'intervalle exponentielle est une bonne nouvelle tentative de la stratégie que de simplement essayer x nombre de fois. Vous pouvez utiliser une bibliothèque comme Polly pour la mettre en œuvre.
J'ai besoin d'une méthode qui prend en charge l'annulation, alors que j'y étais, j'ai ajouté le support pour le retour des intermédiaires échecs.
Vous pouvez utiliser le
Retry
fonction comme ceci, recommencer 3 fois avec 10 secondes de retard, mais sans annulation.Ou, recommencer éternellement toutes les cinq secondes, à moins d'annulation.
Comme vous pouvez le deviner, Dans mon code source j'ai surchargé le
Retry
fonction pour prendre en charge les différentes delgate types je souhaite faire usage de.Ou que diriez-vous faire un peu plus propre....
Je crois, de lever des exceptions doit généralement être évitée comme un mécanisme à moins que votre un les passer entre les limites (comme la construction d'une bibliothèque, d'autres personnes peuvent l'utiliser). Pourquoi ne pas simplement avoir la
DoSomething()
de retour de commandetrue
si elle a été un succès etfalse
autrement?EDIT: Et cela peut être encapsulé dans une fonction comme d'autres l'ont suggéré. Seul problème est que si vous n'êtes pas de l'écriture de la
DoSomething()
fonction de vous-mêmeJ'ai eu le besoin d'en passer un paramètre à ma méthode de réessayer, et ont une valeur de résultat; j'ai donc besoin d'une expression..
J'ai créer cette classe qui fait le travail (il est inspiré de la LBushkin en a une)
vous pouvez l'utiliser comme ceci:
ps.
le LBushkin la solution de ne plus retry =D
Je voudrais ajouter le code suivant à la accepté de répondre à
Fondamentalement le code ci-dessus est de rendre le
Retry
classe générique de sorte que vous pouvez passer le type de l'exception que vous voulez prendre pour une nouvelle tentative.Maintenant il est presque de la même manière, mais en spécifiant le type d'exception
Je sais que cette réponse est très vieux, mais je voulais juste commenter ce parce que j'ai couru dans des problèmes à l'aide de ces tout, faire, quelle que soit la déclaration avec les compteurs.
Au fil des années, j'ai réglé sur une meilleure approche je pense. C'est d'utiliser une sorte d'agrégation d'événements comme un réactif extensions "Sujet" ou similaire. Lorsqu'un essai échoue, il vous suffit de publier un événement en disant: l'essai a échoué, et qui ont l'agrégateur fonction de re-planifier l'événement. Cela permet beaucoup plus de contrôle sur le recommencer sans polluer l'appel lui-même avec un tas de réessayer de boucles et de ce pas. Ni à vous attacher à un seul thread avec un tas de fil dort.
Faire simple en C#, Java ou d'autres langages:
et à utiliser dans votre code très simple:
ou vous pouvez l'utiliser dans récursive méthodes:
Request.BadRequest
?throw;
est la seule façon de la boucle infinie est terminée, cette méthode est en fait la mise en œuvre de "l'essayer jusqu'à ce qu'il échoue N fois" et pas l'option "essayer jusqu'àN
fois jusqu'à ce qu'elle réussisse". Vous avez besoin d'unbreak;
oureturn;
après l'appel àThingToTryDelegate();
sinon ça va être appelé en permanence si elle n'échoue jamais. Aussi, ce ne compile pas parce que le premier paramètre deTryNTimes
n'a pas de nom. -1.J'ai écrit une petite classe basée sur les réponses postées ici. J'espère que ça va aider quelqu'un: https://github.com/natenho/resiliency