Un pipeline alimenta a un consumidor de streams

Empate async

Requisito

Obtén cinco estados de pedido, como mucho dos peticiones en vuelo, resultados en orden de origen — y luego entrégalos a un consumidor aguas abajo que los agrupa en pares e imprime cada lote. Los retardos de consulta están fijados en el código; las dos versiones deben imprimir las líneas que aparecen bajo Salida esperada.

Salida esperada
batch 1: A-101 shipped | A-102 packed
batch 2: A-103 allocated | A-104 delayed
batch 3: A-105 shipped

Lado a lado

RxDart

FxDart

Por qué difieren

RxDart corre streams de punta a punta: flatMap con maxConcurrent: 2 acota los fetches y bufferCount(2) empareja los resultados. Una salvedad vive en el medio: el flatMap concurrente emite en orden de terminación, así que este panel solo imprime en orden de origen porque los retardos dan la casualidad de completarse así — el reordenamiento bajo concurrencia es el comportamiento por defecto del modelo push, y conservar el orden de origen en general significa recoger y ordenar.

El panel FxDart es el puente del ejemplo anterior cruzado en la otra dirección. La mitad de los fetches es un pipeline pull — mapConcurrent(2, …) es ordenado por construcción, hagan lo que hagan los retardos — chunk(2) (el bufferCount de FxDart) empareja los resultados, y toStream() entrega los lotes a cualquier consumidor de streams. Aquí ese consumidor solo imprime, y en una app RxDart podría seguir con operadores Rx sobre el stream puenteado: al productor no le importa quién consume. Esa división del trabajo es el veredicto: haz el trabajo acotado, ordenado y tipado en el pipeline pull, exponlo como un Stream, y deja que el mundo push tome el relevo donde el vocabulario push (buffering, debouncing, enlace de UI) encaja mejor. Empate — el puente es el punto.

Benchmark

Apple M1 Max, 32 GB de RAM · Dart 3.12.2 (compilado AOT) · 2026-08-18

Caso async: la escala principal es N = 10,000, no 1,000,000. Cada elemento cuesta una vuelta del event loop en ambos lados, así que un millón de awaits reales mediría el event loop de Dart durante minutos — no el pipeline. Los retardos son de longitud cero y se conserva el límite de concurrencia del ejemplo; lo que comparan las barras es la maquinaria del pipeline.

N = 100

Tiempo Empate

RxDart 438 µs
FxDart 415 µs

Memoria pico Empate

RxDart 16.6 MB
FxDart 17.2 MB

N = 10,000

Tiempo Empate

RxDart 36.4 ms
FxDart 36.3 ms

Memoria pico Gana FxDart

RxDart 50.5 MB
FxDart 30.8 MB

Las barras son medianas de iteraciones cronometradas repetidas en procesos nuevos por lado (los N pequeños se agrupan por resolución del temporizador). Dos lados a menos del 5% entre sí — o a menos de 0.6 ms, una diferencia que nadie puede percibir — cuentan como empate; las carreras relativas ajustadas se vuelven a medir hasta 5 veces. En una app, cualquier cosa por debajo de unos pocos milisegundos es invisible para el usuario, gane la barra que gane. La memoria es el RSS pico del proceso. La VM de Dart y el dataset son idénticos en ambos lados, así que la diferencia entre las dos barras es lo que retiene el pipeline en sí.