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.
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.
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