concurrent

Evalúa hasta n elementos de un pipeline asíncrono a la vez, y aun así los resultados llegan en el orden de la fuente.

FxAsyncIterable<A> concurrentAsync<A>(int length, FxAsyncIterable<A> iterable) FxAsync<T> FxAsync.concurrent(int length) // chain

Lección

concurrent(n) es la respuesta de FxDart a «ejecuta varios pasos asíncronos en paralelo, pero mantén los resultados en orden». Funciona mediante el modelo de marcador de concurrencia integrado en FxAsyncIterator.next([Concurrent? concurrent]): cuando llamas a .concurrent(3), cada pull que lo atraviesa propaga un marcador Concurrent(3) aguas arriba, capa a capa, diciéndole a quien produzca los valores «evalúa 3 de golpe en vez de uno». El operador de aguas arriba — normalmente un map perezoso — ve ese marcador y, en lugar de esperar un Future y después arrancar el siguiente, llama tres veces al next() de su propia fuente sin esperar entre medias, de modo que hay tres Futures en vuelo a la vez. A medida que cada uno se resuelve, concurrent guarda el resultado en un búfer pero solo te entrega valores en el orden original — así que los resultados siempre salen respetando la secuencia de entrada, aunque un elemento posterior termine antes que uno anterior.

Este es el canal de retorno que mencionaba la lección de toAsync: el Stream de Dart no tiene forma de pedirle a posteriori a una fuente de aguas arriba «dame 3 a la vez», porque un Stream empuja los valores a su propio ritmo. El protocolo next() basado en pull de FxDart lleva esa petición aguas arriba en cada pull, y eso es justamente lo que hace posible concurrent(n).

Ajusta n según lo que te limite: una API REST puede tolerar de 5 a 10 peticiones concurrentes, y n = 1 equivale a esperar secuencialmente sin más (que es exactamente lo que obtienes sin concurrent). El trabajo limitado por CPU es parallel, no una n más grande — véase concurrent or parallel.

Con N=100k de un fetch sin retardo, un pool de workers escrito a mano sigue siendo un 10% más rápido (AOT). Ese coste restante es la maquinaria de lotes ordenados, no la capa map — lo pagas por la cadena.

Demo 1 · Secuencial vs. concurrent(3), cronometrado

Seis elementos, cada uno con 200ms de retardo. En secuencia tarda ~1200ms; pidiendo 3 a la vez baja a ~400ms:

Demo 2 · El orden se preserva, aunque el de finalización no

Aquí el elemento 2 termina antes que el 1 (100ms frente a 300ms), pero concurrent(3) sigue devolviendo los resultados en el orden de la fuente — compáralo con concurrentPool, que hace justo lo contrario:

Pruébalo tú

Ejercicio: este pipeline procesa 6 elementos de 200ms cada uno, de forma secuencial (n = 1). Sube n y observa cómo cae el tiempo transcurrido — prueba con 3 y luego con 6.

Relacionado: concurrentPool — variante por orden de finalización · toAsync — el modelo basado en pull en el que se apoya · variantes asíncronas — la convención de nombres *Async · map — el operador que más a menudo acompaña a concurrent · concurrent or parallel — I/O vs CPU