Descargas en paralelo, resultados en orden

Gana FxDart async

Requisito

Descarga seis archivos — cada uno con un tamaño fijo y un tiempo de transferencia fijo (simulado), en el código de abajo — con como mucho tres descargas en vuelo, y lista los resultados numerados en el orden en que se pidieron. Los retardos están elegidos para que las finalizaciones se entrelacen: el video.mp4 de 30 ms se pide primero, pero el notes.txt de 10 ms termina antes. Ambas versiones imprimen qué archivo terminó primero y la concurrencia máxima observada — la prueba de que el trabajo se solapó de verdad, fuera de orden, mientras el listado se mantenía en orden.

Esa garantía de reordenar por dentro y conservar el orden en la superficie es justo lo que hace concurrent(3): evalúa hasta tres elementos aguas arriba a la vez y aun así entrega los resultados en el orden de la fuente. La cadena los numera con zipWithIndex y monta el informe con join.

Salida esperada
downloaded 6 files, 3 at a time, in request order:
  1. video.mp4 (900 KB)
  2. notes.txt (2 KB)
  3. slides.pdf (340 KB)
  4. photo.jpg (180 KB)
  5. data.csv (55 KB)
  6. theme.zip (260 KB)
first to finish: notes.txt
total size: 1737 KB
max downloads in flight: 3

Lado a lado

Dart nativo

FxDart

Por qué difieren

Future.wait conserva el orden, pero descarga todo a la vez — sin límite. Añadir el límite es lo que obliga a montar el pool de workers nativo, y conservar el orden bajo ese pool es precisamente la parte sutil: la lista results predimensionada e indexada por un cursor compartido. Si te equivocas en la contabilidad de las posiciones, los resultados vuelven desordenados — un bug que solo se manifiesta cuando el orden de finalización resulta ser distinto del orden de petición, algo que depende de los tiempos y es fácil que se escape en los tests. En FxDart la garantía de orden es el contrato del operador, no tu código: concurrent(3) no puede devolver elementos desordenados, caigan como caigan los tiempos.

Benchmark

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

Caso async: la escala principal es N = 100,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

Dart nativo 373 µs
FxDart 444 µs

Memoria pico Empate

Dart nativo 16.5 MB
FxDart 17.1 MB

N = 10,000

Tiempo Gana nativo

Dart nativo 31.8 ms
FxDart 37.3 ms

Memoria pico Gana FxDart

Dart nativo 50.8 MB
FxDart 33.0 MB

N = 100,000

Tiempo Gana nativo

Dart nativo 338.8 ms
FxDart 380.5 ms

Memoria pico Gana nativo

Dart nativo 80.9 MB
FxDart 92.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í.