await - programacion asincrona c# ejemplos
¿Cuál es la mejor manera de agregar un mecanismo de reintento/retroceso para las tareas de sincronización/asíncrono en C#? (5)
Algún código que puede ayudarte a lograr tu objetivo.
public static class Retry
{
public static void Do(
Action action,
TimeSpan retryInterval,
int retryCount = 3)
{
Do<object>(() =>
{
action();
return null;
}, retryInterval, retryCount);
}
public static T Do<T>(
Func<T> action,
TimeSpan retryInterval,
int retryCount = 3)
{
var exceptions = new List<Exception>();
for (int retry = 0; retry < retryCount; retry++)
{
try
{
if (retry > 0)
Thread.Sleep(retryInterval);
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
Llame y vuelva a intentarlo a continuación:
int result = Retry.Do(SomeFunctionWhichReturnsInt, TimeSpan.FromSeconds(1), 4);
Imagine una aplicación WebForms donde existe un método principal llamado CreateAll (). Puedo describir el proceso de las tareas del método paso a paso de la siguiente manera:
1) Almacena en la base de datos (Actualizar / Crear elementos de Db 3-4 veces)
2) Inicia un nuevo hilo.
3) Result1 = Llama a un servicio de jabón y, al utilizar un umbral de tiempo de espera, comprueba el estado y después de x minutos. Continúa (el estado ahora es correcto y no significa fallo)
4) Almacena en la base de datos (Actualizar / Crear elementos de Db 3-4 veces)
5) result2 = Llama a un servicio de jabón (En caso de incendio y olvido)
6) Actualiza un archivo de configuración (que se toma en realidad del resultado 1)
7) Al utilizar solicitudes de devolución de llamada, comprueba cada x segundos en la parte frontal del estado del resultado2 y la interfaz de usuario muestra una barra de progreso. Si el proceso se completa (100%), significa éxito
Estoy considerando que todas ellas son tareas que pueden agruparse por su tipo. Básicamente, los distintos tipos de acciones son:
- Tipo1: transacción DB
- Tipo2: Servicio de comunicación / transacción
- Tipo 3: Config transacciones de E / S de archivos
Quiero agregar un mecanismo de reversión / reintento a la implementación existente y usar una arquitectura orientada a tareas y refactorizar el código heredado existente.
Encontré que algo como Memento Design Pattern O Command Pattern en C # podría ayudar para este propósito. También me pareció interesante la descripción de la prueba de reintento de msdn. Realmente no lo sé y quiero que alguien me guíe a la decisión más segura y mejor ...
¿Me puede sugerir la mejor manera para este caso de mantener la implementación existente y el flujo, pero envolviéndolo en una implementación general y abstracta de reintento / restauración / lista de tareas?
La implementación final debe poder volver a intentarlo en cada caso (sea cual sea la tarea o el error general, como el tiempo de espera, etc., durante el proceso general de creación de todos) y también habrá una lista de decisiones de reversión en la que la aplicación debe poder deshacer todas las tareas que se realizaron. .
Quiero algunos ejemplos de cómo romper este código acoplado.
PseudoCódigo que podría ser útil:
class something
{
static result CreateAll(object1 obj1, object2 obj2 ...)
{
//Save to database obj1
//...
//Update to database obj1
//
//NEW THREAD
//Start a new thread with obj1, obj2 ...CreateAll
//...
}
void CreateAllAsync()
{
//Type1 Save to database obj1
//...
//Type1 Update to database obj2
//Type2 Call Web Service to create obj1 on the service (not async)
while (state != null && now < times)
{
if (status == "OK")
break;
else
//Wait for X seconds
}
//Check status continue or general failure
//Type1 Update to database obj2 and obj1
//Type2 Call Web Service to create obj2 on the service (fire and forget)
//Type3 Update Configuration File
//Type1 Update to database obj2 and obj1
//..
return;
}
//Then the UI takes the responsibility to check the status of result2
Bueno ... suena como una situación realmente desagradable. No puedes abrir una transacción, escribir algo en la base de datos e ir a pasear a tu perro en el parque. Porque las transacciones tienen esta mala costumbre de bloquear recursos para todos. Esto elimina su mejor opción: transacciones distribuidas.
Ejecutaría todas las operaciones y prepararía un script inverso a medida que avanzaba. Si la operación es un éxito, purgaría el script. De lo contrario lo ejecutaría. Pero esto está abierto a posibles trampas y el script debe estar listo para manejarlos. Por ejemplo, ¿qué pasaría si a medio tiempo alguien ya haya actualizado los registros que agregó? ¿O calculaste un agregado basado en tus valores?
Aún así: construir una secuencia de comandos inversa es la solución simple, no hay ciencia espacial allí. Sólo
List<Command> reverseScript;
y luego, si necesita revertir:
using (TransactionScope tx= new TransactionScope()) {
foreach(Command cmd in reverseScript) cmd.Execute();
tx.Complete();
}
Mira a usar Polly para reintentar los escenarios que parecen alinearse bien con tu pseudo código. Al final de esta respuesta hay una muestra de la documentación. Puede hacer todo tipo de escenarios de reintento, reintento y espera, etc. Por ejemplo, puede reintentar una transacción completa varias veces, o alternativamente reintentar un conjunto de acciones idempotentes varias veces y luego escribir la lógica de compensación si / cuando el reintento La política finalmente falla.
Un patrón de recuerdo es más para deshacer-rehacer la lógica que encontraría en un procesador de textos (Ctrl-Z y Ctrl-Y).
Otros patrones útiles a considerar son una cola simple, una cola persistente o incluso un bus de servicio para darle consistencia eventual sin tener que hacer que el usuario espere a que todo se complete con éxito.
// Retry three times, calling an action on each retry
// with the current exception and retry count
Policy
.Handle<DivideByZeroException>()
.Retry(3, (exception, retryCount) =>
{
// do something
});
Una muestra basada en su Pseudo-Código puede verse como sigue:
static bool CreateAll(object1 obj1, object2 obj2)
{
// Policy to retry 3 times, waiting 5 seconds between retries.
var policy =
Policy
.Handle<SqlException>()
.WaitAndRetry(3, count =>
{
return TimeSpan.FromSeconds(5);
});
policy.Execute(() => UpdateDatabase1(obj1));
policy.Execute(() => UpdateDatabase2(obj2));
}
Para mí, esto suena como ''Transacciones distribuidas'', ya que tiene diferentes recursos (base de datos, comunicación de servicio, archivo i / o) y desea realizar una transacción que involucre a todos ellos.
En C # podría resolver esto con el Coordinador de transacciones distribuidas de Microsoft . Para cada recurso necesitas un administrador de recursos. Para las bases de datos, como el servidor sql y el archivo i / o, ya está disponible, que yo sepa. Para otros puedes desarrollar el tuyo propio.
Como ejemplo, para ejecutar estas transacciones puede usar la clase TransactionScope
la siguiente manera:
using (TransactionScope ts = new TransactionScope())
{
//all db code here
// if an error occurs jump out of the using block and it will dispose and rollback
ts.Complete();
}
(Ejemplo tomado de here )
Para desarrollar su propio administrador de recursos, debe implementar IEnlistmentNotification
y eso puede ser una tarea bastante compleja. Aquí hay un breve example .
Puede optar por el patrón de Comando donde cada comando contiene toda la información necesaria, como la cadena de conexión, la URL de servicio, el conteo de reintentos, etc. Además de esto, puede considerar bloques de flujo de datos de rx para hacer la tubería.
Actualización: la intención es tener la separación de la preocupación. La lógica de reintento se limita a una clase que es un envoltorio al comando existente. Puede realizar más análisis y crear los objetos de comando, invocador y receptor adecuados y agregar la funcionalidad de retroceso.
public abstract class BaseCommand
{
public abstract RxObservables Execute();
}
public class DBCommand : BaseCommand
{
public override RxObservables Execute()
{
return new RxObservables();
}
}
public class WebServiceCommand : BaseCommand
{
public override RxObservables Execute()
{
return new RxObservables();
}
}
public class ReTryCommand : BaseCommand // Decorator to existing db/web command
{
private readonly BaseCommand _baseCommand;
public RetryCommand(BaseCommand baseCommand)
{
_baseCommand = baseCommand
}
public override RxObservables Execute()
{
try
{
//retry using Polly or Custom
return _baseCommand.Execute();
}
catch (Exception)
{
throw;
}
}
}
public class TaskDispatcher
{
private readonly BaseCommand _baseCommand;
public TaskDispatcher(BaseCommand baseCommand)
{
_baseCommand = baseCommand;
}
public RxObservables ExecuteTask()
{
return _baseCommand.Execute();
}
}
public class Orchestrator
{
public void Orchestrate()
{
var taskDispatcherForDb = new TaskDispatcher(new ReTryCommand(new DBCommand));
var taskDispatcherForWeb = new TaskDispatcher(new ReTryCommand(new WebCommand));
var dbResultStream = taskDispatcherForDb.ExecuteTask();
var WebResultStream = taskDispatcherForDb.ExecuteTask();
}
}