Todas las llamadas de servicio en mi aplicación se implementan como tasks.When cada vez se abre una tarea, necesito presentar al usuario un cuadro de diálogo para volver a intentar la última operación failed.If el usuario elige volver a intentar el programa debe volver a intentar la tarea, de lo contrario la ejecución del programa debe continuar después de entrar el exception.Any tiene una idea de alto nivel sobre cómo implementar esta funcionalidad?reintentar una tarea varias veces sobre la base de la entrada del usuario en caso de una excepción en tarea
Respuesta
ACTUALIZACIÓN 5/2017
C# 6 filtros de excepción hacen la cláusula catch
mucho más simple:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await Task.Run(func);
return result;
}
catch when (retryCount-- > 0){}
}
}
y una versión recursiva:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
try
{
var result = await Task.Run(func);
return result;
}
catch when (retryCount-- > 0){}
return await Retry(func, retryCount);
}
ORIGINAL
Hay muchas maneras de codificar una función de reintento: se puede usar recursión o iteración tarea. Hubo un discussion en el grupo de usuarios griego .NET hace un tiempo sobre las diferentes formas de hacer exactamente esto.
Si está utilizando F # También se puede usar construcciones asíncronas. Desafortunadamente, no se pueden usar las construcciones async/await al menos en el Async CTP, porque al código generado por el compilador no le gustan los múltiples tiempos de espera o los posibles retiros en los bloques catch.
La versión recursiva es quizás la forma más sencilla de construir un reintento en C#.La siguiente versión no utiliza Separar y añade un retardo opcional antes reintentos:
private static Task<T> Retry<T>(Func<T> func, int retryCount, int delay, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
tcs = new TaskCompletionSource<T>();
Task.Factory.StartNew(func).ContinueWith(_original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
tcs.SetException(_original.Exception.InnerExceptions);
else
Task.Factory.StartNewDelayed(delay).ContinueWith(t =>
{
Retry(func, retryCount - 1, delay,tcs);
});
}
else
tcs.SetResult(_original.Result);
});
return tcs.Task;
}
La función StartNewDelayed proviene de los ParallelExtensionsExtras muestras y utiliza un temporizador para activar un TaskCompletionSource cuando se produce el tiempo de espera.
La versión # F es mucho más simple:
let retry (asyncComputation : Async<'T>) (retryCount : int) : Async<'T> =
let rec retry' retryCount =
async {
try
let! result = asyncComputation
return result
with exn ->
if retryCount = 0 then
return raise exn
else
return! retry' (retryCount - 1)
}
retry' retryCount
Desafortunadamente, no es posible escribir algo similar en C# usando asíncrono/espera de la asíncrono CTP porque el compilador no le gusta declaraciones aguardan en el interior un bloque de captura. El siguiente intento también falla Silenty, debido a que el tiempo de ejecución no le gusta encontrarse con un descansar tras una excepción:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await TaskEx.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
retryCount--;
}
}
}
En cuanto a preguntar al usuario, puede modificar Reintentar para llamar a una función que pide al usuario y devuelve una tarea a través de un TaskCompletionSource para activar el siguiente paso cuando el usuario responde, por ejemplo:
private static Task<bool> AskUser()
{
var tcs = new TaskCompletionSource<bool>();
Task.Factory.StartNew(() =>
{
Console.WriteLine(@"Error Occured, continue? Y\N");
var response = Console.ReadKey();
tcs.SetResult(response.KeyChar=='y');
});
return tcs.Task;
}
private static Task<T> RetryAsk<T>(Func<T> func, int retryCount, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
tcs = new TaskCompletionSource<T>();
Task.Factory.StartNew(func).ContinueWith(_original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
tcs.SetException(_original.Exception.InnerExceptions);
else
AskUser().ContinueWith(t =>
{
if (t.Result)
RetryAsk(func, retryCount - 1, tcs);
});
}
else
tcs.SetResult(_original.Result);
});
return tcs.Task;
}
con todas las continuaciones, se puede ver por qué una versión asíncrona de reintento es tan deseable.
ACTUALIZACIÓN:
En Visual Studio 2012 Beta las dos versiones siguientes trabajos:
Una versión con un bucle while:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await Task.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
retryCount--;
}
}
}
y una versión recursiva:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
try
{
var result = await Task.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
}
return await Retry(func, --retryCount);
}
Gracias por una explicación detallada, probaré esto y le dejaré saber –
+1 Buen trabajo. [Agregó un riff debajo] (http://stackoverflow.com/a/16354355/11635) –
Cuando en el nivel alto, me parece que ayuda a hacer una firma de función de lo que tienes y lo que quiere.
tiene:
- Una función que le da una tarea (
Func<Task>
). Usaremos la función porque las tareas en sí no son recuperables en general. - Una función que determina si la tarea se ha completado o total debe ser juzgado (
Func<Task, bool>
)
que desee:
- Una tarea general
por lo que tendrá una funcionan como:
Task Retry(Func<Task> action, Func<Task, bool> shouldRetry);
Extendiendo la práctica dentro de la función, las tareas tienen prácticamente 2 operaciones que hacer con ellas, lea su estado y ContinueWith
. Para hacer sus propias tareas, TaskCompletionSource
es un buen punto de partida. Un primer intento podría ser algo como:
//error checking
var result = new TaskCompletionSource<object>();
action().ContinueWith((t) =>
{
if (shouldRetry(t))
action();
else
{
if (t.IsFaulted)
result.TrySetException(t.Exception);
//and similar for Canceled and RunToCompletion
}
});
El problema obvio aquí es que sólo 1 de reintento volverá a suceder. Para evitarlo, debe hacer que la función se llame a sí misma. La forma habitual de hacer esto con lambdas es algo como esto:
//error checking
var result = new TaskCompletionSource<object>();
Func<Task, Task> retryRec = null; //declare, then assign
retryRec = (t) => { if (shouldRetry(t))
return action().ContinueWith(retryRec).Unwrap();
else
{
if (t.IsFaulted)
result.TrySetException(t.Exception);
//and so on
return result.Task; //need to return something
}
};
action().ContinueWith(retryRec);
return result.Task;
Gracias, lo hará intente y le deje saber –
Aquí hay una versión riffed de Panagiotis Kanavos's excellent answer que he probado y estoy usando en producción.
trata de resolver algunas cosas que eran importantes para mí:
- ¿Desea poder decidir si vuelve a intentar función del número de intentos anteriores y excepción del intento actual
- no quiero depender de
async
(menos restricciones medio ambiente) - quiere tener la
Exception
lo que resulta en el caso de fallo incluyen detalles de cada intento
static Task<T> RetryWhile<T>(
Func<int, Task<T>> func,
Func<Exception, int, bool> shouldRetry)
{
return RetryWhile<T>(func, shouldRetry, new TaskCompletionSource<T>(), 0, Enumerable.Empty<Exception>());
}
static Task<T> RetryWhile<T>(
Func<int, Task<T>> func,
Func<Exception, int, bool> shouldRetry,
TaskCompletionSource<T> tcs,
int previousAttempts, IEnumerable<Exception> previousExceptions)
{
func(previousAttempts).ContinueWith(antecedent =>
{
if (antecedent.IsFaulted)
{
var antecedentException = antecedent.Exception;
var allSoFar = previousExceptions
.Concat(antecedentException.Flatten().InnerExceptions);
if (shouldRetry(antecedentException, previousAttempts))
RetryWhile(func,shouldRetry,previousAttempts+1, tcs, allSoFar);
else
tcs.SetException(allLoggedExceptions);
}
else
tcs.SetResult(antecedent.Result);
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
- 1. Cómo forzar una excepción de una tarea para ser observada en una tarea de continuación?
- 2. Caso de uso para la tarea buildNeeded?
- 3. Programación en Linux: ejecuta una tarea cuando la computadora está inactiva (= no hay entrada de usuario)
- 4. Ejecutar una tarea predeterminada en ANT en caso de falla
- 5. ¿Agregar una tarea programada en la configuración?
- 6. Rails Tarea de fondo de la tarea
- 7. ¿La mejor manera de planificar una tarea?
- 8. ¿Cómo coloco una tarea nuevamente en la cola si la tarea falla?
- 9. ¿Cómo forzar la cancelación de una tarea?
- 10. ¿Ejecutar SQL en una tarea de Gradle?
- 11. Finalización de la tarea del iPhone
- 12. Capistrano Comprobación de una variable indefinida en la Tarea
- 13. ¿Cómo adjuntar una tarea personalizada para ejecutar antes de la tarea de prueba en sbt?
- 14. Imprimir en la pantalla en una tarea de rastreado
- 15. ContinueWith una tarea cancelada
- 16. Obtiene la ID del intento de tarea para la tarea Hadoop actualmente en ejecución
- 17. cómo hacer una vista rabl en una tarea de rake?
- 18. programación de la tarea de apio (Asegurar una tarea se ejecuta sólo uno a la vez)
- 19. entrada Pasando a la tarea de Ant <exec>
- 20. Detener una tarea periódica desde dentro de la tarea que se ejecuta en un ScheduledExecutorService
- 21. Cómo hacer que una tarea de apio falle dentro de la tarea?
- 22. Configuración de una tarea cron en Windows
- 23. ¿Hay alguna manera de comenzar una tarea utilizando la tarea ContinueWith?
- 24. Configurar una tarea programada en .Net
- 25. Cómo programar una tarea para su futura ejecución en la Tarea Biblioteca paralela
- 26. reintentar una ruta migratoria fallado la migración
- 27. ¿Qué devuelve una tarea?
- 28. ¿Debo deshacerme de una tarea?
- 29. Cancelar una tarea
- 30. La mejor manera de sincronizar una tarea asíncrona
@svick no he tratado de implementar esta funcionalidad está llegando a una tarea futura, Parece que tenemos ideas interesantes ya, tiene que ir a través de ellos en detalle y darle una oportunidad –