Ocasionalmente, necesito volver a intentar una operación varias veces antes de rendirme. Mi código es como:
int retries = 3;
while(true) {
try {
DoSomething();
break; // success!
} catch {
if(--retries == 0) throw;
else Thread.Sleep(1000);
}
}
Me gustaría volver a escribir esto en una función de reintento general como:
TryThreeTimes(DoSomething);
¿Es posible en C #? ¿Cuál sería el código para el TryThreeTimes()
método?
Respuestas:
Las declaraciones generales de captura que simplemente reintentan la misma llamada pueden ser peligrosas si se usan como un mecanismo general de manejo de excepciones. Dicho esto, aquí hay un contenedor de reintentos basado en lambda que puede usar con cualquier método. Elegí factorizar el número de reintentos y el tiempo de espera de reintentos como parámetros para un poco más de flexibilidad:
Ahora puede usar este método de utilidad para realizar la lógica de reintento:
o:
o:
O incluso podrías hacer una
async
sobrecarga.fuente
Policy.Handle<DivideByZeroException>().WaitAndRetry(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Deberías probar Polly . Es una biblioteca .NET escrita por mí que permite a los desarrolladores expresar políticas transitorias de manejo de excepciones como Retry, Retry Forever, Wait and Retry o Circuit Breaker de manera fluida.
Ejemplo
fuente
Posiblemente sea una mala idea. Primero, es emblemático de la máxima "la definición de locura es hacer lo mismo dos veces y esperar resultados diferentes cada vez". En segundo lugar, este patrón de codificación no se compone bien consigo mismo. Por ejemplo:
Suponga que su capa de hardware de red reenvía un paquete tres veces en caso de falla, esperando, por ejemplo, un segundo entre fallas.
Ahora suponga que la capa de software reenvía una notificación sobre una falla tres veces en la falla del paquete.
Ahora suponga que la capa de notificación reactiva la notificación tres veces en un error de entrega de notificación.
Ahora suponga que la capa de informe de errores reactiva la capa de notificación tres veces en un error de notificación.
Y ahora suponga que el servidor web reactiva el error informando tres veces en caso de error.
Y ahora suponga que el cliente web reenvía la solicitud tres veces al recibir un error del servidor.
Ahora suponga que la línea en el conmutador de red que debe enrutar la notificación al administrador está desconectada. ¿Cuándo finalmente el usuario del cliente web recibe su mensaje de error? Lo hago unos doce minutos después.
No sea que piense que esto es solo un ejemplo tonto: hemos visto este error en el código del cliente, aunque mucho, mucho peor de lo que he descrito aquí. En el código de cliente en particular, la brecha entre la condición de error que sucedió y finalmente se informó al usuario fue de varias semanas porque muchas capas estaban volviendo a intentarlo automáticamente con esperas. Solo imagine lo que sucedería si hubiera diez reintentos en lugar de tres .
Por lo general, lo correcto con una condición de error es informarlo de inmediato y dejar que el usuario decida qué hacer. Si el usuario desea crear una política de reintentos automáticos, permítale crear esa política en el nivel apropiado en la abstracción del software.
fuente
Entonces llamarías:
...o alternativamente...
Una opción más flexible:
Para ser utilizado como:
Una versión más moderna con soporte para async / wait:
Para ser utilizado como:
fuente
--retryCount <= 0
porque esto continuará para siempre si desea deshabilitar los reintentos configurándolo en 0. Técnicamente el términoretryCount
no es realmente un buen nombre, porque no volverá a intentarlo si lo configura en 1. o renombrar atryCount
, o poner el - atrás.Thread.Sleep
. Las alternativas son usar temporizadores, o más probablemente hoy en díaasync
para volver a intentarlo, conTask.Delay
.returns true
?Func<bool>
El bloque de aplicación Transitor Fault Handling proporciona una colección extensible de estrategias de reintento que incluyen:
También incluye una colección de estrategias de detección de errores para servicios basados en la nube.
Para obtener más información, consulte este capítulo de la Guía del desarrollador.
Disponible a través de NuGet (busque ' topaz ').
fuente
Permitir funciones y reintentar mensajes
fuente
max retries
?También puede considerar agregar el tipo de excepción para el que desea volver a intentar. Por ejemplo, ¿es esta una excepción de tiempo de espera que desea volver a intentar? ¿Una excepción de base de datos?
También puede observar que todos los otros ejemplos tienen un problema similar con la prueba de reintentos == 0 y reintentan infinito o no generan excepciones cuando se les da un valor negativo. También Sleep (-1000) fallará en los bloques catch anteriores. Depende de cuán 'tonto' esperes que sea la gente, pero la programación defensiva nunca está de más.
fuente
Soy fanático de los métodos de recursión y extensión, así que aquí están mis dos centavos:
fuente
Sobre la base del trabajo anterior, pensé en mejorar la lógica de reintento de tres maneras:
Haciéndolo un
Action
método de extensiónEl método se puede invocar así (también se pueden usar métodos anónimos, por supuesto):
fuente
Mantenlo simple con C # 6.0
fuente
Use Polly
https://github.com/App-vNext/Polly-Samples
Aquí hay un reintento genérico que uso con Polly
Úselo así
fuente
Implementé la respuesta de LBushkin de la última manera:
y para usarlo:
mientras que la función
[TaskFunction]
puede serTask<T>
o simplementeTask
.fuente
Implementaría esto:
No usaría excepciones como se usan en los otros ejemplos. Me parece que si esperamos la posibilidad de que un método no tenga éxito, su falla no es una excepción. Entonces, el método al que llamo debería devolver verdadero si tuvo éxito, y falso si falló.
¿Por qué es un
Func<bool, bool>
y no solo unFunc<bool>
? De modo que si quiero un método para poder lanzar una excepción en caso de falla, tengo una manera de informarle que este es el último intento.Entonces podría usarlo con código como:
o
Si pasa un parámetro que el método no uso resulta ser incómodo, es trivial para aplicar una sobrecarga de
Retry
que sólo se necesita unaFunc<bool>
también.fuente
StopIteration
excepción), y hay una razón.TryDo
patrón del método es una pendiente resbaladiza. Antes de que te des cuenta, toda tu pila de llamadas consistirá enTryDo
métodos. Se inventaron excepciones para evitar tal desorden.El retroceso exponencial es una buena estrategia de reintento que simplemente intentar x número de veces. Puede usar una biblioteca como Polly para implementarlo.
fuente
Para aquellos que desean tener la opción de volver a intentar cualquier excepción o establecer explícitamente el tipo de excepción, use esto:
fuente
Necesitaba un método que admita la cancelación, mientras lo hacía, agregué soporte para devolver fallas intermedias.
Puede usar la
Retry
función de esta manera, vuelva a intentarlo 3 veces con un retraso de 10 segundos pero sin cancelación.O, vuelva a intentarlo eternamente cada cinco segundos, a menos que se cancele.
Como puede adivinar, en mi código fuente he sobrecargado la
Retry
función para admitir los diferentes tipos de delegado que deseo usar.fuente
Este método permite reintentos en ciertos tipos de excepción (arroja otros inmediatamente).
Ejemplo de uso:
fuente
Actualización después de 6 años: ahora considero que el enfoque a continuación es bastante malo. Para crear una lógica de reintento, deberíamos considerar usar una biblioteca como Polly.
Mi
async
implementación del método de reintento:Puntos clave: solía
.ConfigureAwait(false);
y en suFunc<dynamic>
lugarFunc<T>
fuente
Task.Delay
se llama sin motivo.O qué tal hacerlo un poco más ordenado ...
Creo que, por lo general, debe evitarse lanzar excepciones como mecanismo, a menos que las pase entre límites (como construir una biblioteca que otras personas puedan usar). ¿Por qué no simplemente hacer que el
DoSomething()
comando regresetrue
si fue exitoso ofalse
no?EDITAR: Y esto se puede encapsular dentro de una función como otros han sugerido también. El único problema es si no está escribiendo la
DoSomething()
función usted mismofuente
Tuve la necesidad de pasar algún parámetro a mi método para volver a intentarlo y obtener un valor de resultado; así que necesito una expresión. Construí esta clase que hace el trabajo (está inspirada en la de LBushkin). Puedes usarla así:
PD. la solución de LBushkin hace un intento más = D
fuente
Agregaría el siguiente código a la respuesta aceptada
Básicamente, el código anterior está haciendo que el
Retry
clase sea genérica para que pueda pasar el tipo de excepción que desea capturar para volver a intentarlo.Ahora úselo casi de la misma manera pero especificando el tipo de excepción
fuente
action
no lanza, entoncesDo
regresabreak
alejándose delfor
bucle.Thread.Sleep
se llama sin motivo.Sé que esta respuesta es muy antigua, pero solo quería comentar sobre esto porque me he encontrado con problemas al usar estos mientras, haga, cualquier declaración con contadores.
Con los años me he decidido por un mejor enfoque, creo. Es utilizar algún tipo de agregación de eventos como un "Asunto" de extensiones reactivas o similar. Cuando falla un intento, simplemente publica un evento que dice que el intento falló y que la función del agregador reprograme el evento. Esto le permite mucho más control sobre el reintento sin contaminar la llamada con un montón de bucles de reintento y lo que no. Tampoco estás atando un solo hilo con un montón de hilos.
fuente
Hazlo simple en C #, Java u otros lenguajes:
y úsalo en tu código muy simple:
o puedes usarlo en métodos recursivos:
fuente
fuente
Request.BadRequest
?Retry helper: una implementación genérica de Java que contiene reintentos de tipo retornable y nulo.
Uso:
fuente
Aquí hay una versión
async
/await
que agrega excepciones y admite la cancelación.fuente
fuente
throw;
es la única forma en que se termina el ciclo infinito, este método en realidad está implementando "intentar hasta que falle N veces" y no el deseado "intentar hastaN
veces hasta que tenga éxito". Necesita unabreak;
oreturn;
después de la llamada paraThingToTryDelegate();
que, de lo contrario, se llame continuamente si nunca falla. Además, esto no se compilará porque el primer parámetro deTryNTimes
no tiene nombre. -1.He escrito una pequeña clase basada en las respuestas publicadas aquí. Esperemos que ayude a alguien: https://github.com/natenho/resiliency
fuente
He implementado una versión asíncrona de la respuesta aceptada de esta manera, y parece funcionar bien, ¿algún comentario?
Y llámalo simplemente así:
fuente
Thread.Sleep
? Bloquear un hilo niega los beneficios de la asincronía. También estoy bastante seguro de que laTask DoAsync()
versión debería aceptar un argumento de tipoFunc<Task>
.