¿Cómo planifica su código asíncrono?

7

Creé una biblioteca que es un invocador para un servicio web en otro lugar. La biblioteca expone métodos asíncronos, ya que las llamadas de servicio web son un buen candidato para esa materia.

Al principio todo estaba bien, tenía métodos con operaciones fáciles de entender de manera CRUD, ya que la biblioteca es un tipo de repositorio.

Pero luego la lógica de negocios comenzó a volverse compleja, y algunos de los procedimientos involucran el encadenamiento de muchas de estas operaciones asíncronas, a veces con diferentes rutas dependiendo del valor del resultado, etc. etc.

De repente, todo es muy desordenado, para detener la ejecución en un punto de interrupción no es muy útil, para averiguar qué está pasando o dónde en el proceso la línea de tiempo ha dejado de ser un dolor ... , menos ágil, y atrapar esos errores que ocurren una vez en 1000 veces se convierte en un infierno.

Desde el punto de vista técnico, un repositorio que expone métodos asíncronos parece una buena idea, ya que algunas capas de persistencia podrían tener retrasos y puede utilizar el enfoque asíncrono para aprovechar al máximo su hardware. Pero desde el punto de vista funcional, las cosas se volvieron muy complejas, y considerando esos procedimientos donde se necesitaban una docena de llamadas diferentes ... No sé el valor real de la mejora.

Después de leer sobre TPL por un tiempo, parecía una buena idea para administrar tareas, pero en el momento en que tienes que combinarlas y comenzar a reutilizar la funcionalidad existente, las cosas se vuelven muy complicadas. He tenido una buena experiencia al usarlo para escenarios muy concretos, pero mala experiencia al usarlos en general.

¿Cómo trabajas de forma asíncrona? ¿Lo usas siempre? ¿O solo para procesos de larga ejecución?

    
pregunta NullOrEmpty 25.06.2013 - 17:51

4 respuestas

7

Diseñaría una máquina de estado para cada conjunto de procesos relacionados y relacionados, y realizaría un seguimiento del estado en algún lugar en un objeto de alto nivel, por lo que es más fácil de depurar. Las transiciones de estado se pueden rastrear y las transiciones de estado no válidas se pueden detectar.

    
respondido por el Frank Hileman 27.06.2013 - 03:52
3

Como dice Frank, escriba una máquina de estado para cada parte de la lógica empresarial que requiera múltiples llamadas asíncronas. Entonces, es fácil interrogar al estado cuando se realiza la depuración, porque lo enumeraste.

De hecho, comienza simplemente dibujando el diagrama de estado. Si el diagrama es demasiado complejo (especialmente si tiene varias llamadas asíncronas en vuelo que podrían retornar en varios pedidos), escríbalo como una tabla para asegurarse de que manejó todas las combinaciones posibles.

    
respondido por el Useless 27.06.2013 - 11:25
1

Primero: planifica y desarrolla un código síncrono que funciona perfectamente solo. Luego, se aíslan las partes que deberían ser entrantes y se crean puntos de acceso para esas partes del código.

Este enfoque es el mejor, porque solo agregará un mecanismo de sincronización a pedido.

    
respondido por el Ulterior 27.06.2013 - 14:46
1

Si tiene la capacidad de usar corrutinas (con rendimientos a profundidades arbitrarias de la pila de llamadas) o continuaciones, estas le permiten escribir código asíncrono que se parece mucho al código síncrono; Todo lo que tiene que hacer una llamada asíncrona para parecer sincrónico es suspender la ejecución después de hacer la llamada y programar la reanudación de la ejecución para cuando esté disponible la respuesta a la llamada asíncrona. Esto reduce enormemente la complejidad global aparente . (La complejidad en realidad no desaparece, pero el idioma / tiempo de ejecución maneja la mayoría de las partes difíciles para que no tenga que hacerlo).

Es posible simular este tipo de cosas con hilos. Sin embargo, eso siempre me deja un poco sucio, ya que considero que tener muchos subprocesos enlazados a E / S es una indicación de que el diseño del sistema no es correcto.

Si falla, su mejor enfoque es utilizar estilo de paso de continuación en el que divide su código en partes síncronas y pasa cualquier estado compartido necesario entre ellas (a través de los períodos en que está esperando para que el procesamiento asíncrono produzca un resultado) utilizando un objeto de contexto de algún tipo. Esto es un poco incómodo de entender en abstracto: aquí hay un ejemplo un poco más concreto. Considere este código (pseudo-) síncrono:

function DoWork() {
    let A = "foo";
    let B = 1.23;
    Call1(the.arguments);
    A.append(B);
    Call2(the.arguments);
    B += A.length;
}

DoWork();
print "finished";

Si cambiamos esto al estilo de paso de continuación, obtenemos algo como esto:

function DoWork(doneCallback) {
    let state = new Context();
    state.A = "foo";
    state.B = 1.23;
    state.done = doneCallback;
    DoAsyncCall1(the.arguments, callback=fn()=>DoMoreWork(state));
}
function DoMoreWork(state) {
    state.A.append(state.B);
    DoAsyncCall2(the.arguments, callback=fn()=>DoneTheWork(state));
}
function DoneTheWork(state) {
    state.B += state.A.length;
    state.done(state.A, state.B);
}

DoWork(fn(…)=>print "finished");

Cada sección entre las llamadas a ser asíncronas se convierte en su propia función (también debe hacer el inicio de cualquier bucle que tenga, siempre que tenga una llamada asíncrona, sea el inicio de una función para que pueda puede modelar el bucle devolviéndole la llamada). El state que se transfiere es la clave para hacer que todo esto funcione, y es posible (suponiendo un mecanismo de llamada asíncrono no horrible) tener múltiples juegos de procesamiento a la vez, ya que cada parte solo conoce su pequeño mundo. Sin embargo, es importante tratar de evitar el uso del estado global cuando se hace esto, aunque solo sea por un motivo que lo dejará muy confundido. Es mucho más fácil hacer que todo esto funcione si está utilizando técnicas de programación en gran parte funcionales.

    
respondido por el Donal Fellows 27.06.2013 - 17:23

Lea otras preguntas en las etiquetas